File indexing completed on 2024-04-21 14:56:36

0001 /*
0002     SPDX-FileCopyrightText: 1998 Mark Donohoe <donohoe@kde.org>
0003     SPDX-FileCopyrightText: 2001 Ellis Whitehead <ellis@kde.org>
0004     SPDX-FileCopyrightText: 2007 Andreas Hartmetz <ahartmetz@gmail.com>
0005     SPDX-FileCopyrightText: 2020 David Redondo <kde@david-redondo.de>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "keysequencerecorder.h"
0011 
0012 #include "keyboardgrabber_p.h"
0013 #include "kguiaddons_debug.h"
0014 #include "shortcutinhibition_p.h"
0015 #include "waylandinhibition_p.h"
0016 
0017 #include <QGuiApplication>
0018 #include <QKeyEvent>
0019 #include <QPointer>
0020 #include <QTimer>
0021 #include <QWindow>
0022 
0023 #include <array>
0024 
0025 /// Singleton whose only purpose is to tell us about other sequence recorders getting started
0026 class KeySequenceRecorderGlobal : public QObject
0027 {
0028     Q_OBJECT
0029 public:
0030     static KeySequenceRecorderGlobal *self()
0031     {
0032         static KeySequenceRecorderGlobal s_self;
0033         return &s_self;
0034     }
0035 
0036 Q_SIGNALS:
0037     void sequenceRecordingStarted();
0038 };
0039 
0040 class KeySequenceRecorderPrivate : public QObject
0041 {
0042     Q_OBJECT
0043 public:
0044     // Copy of QKeySequencePrivate::MaxKeyCount from private header
0045     enum { MaxKeyCount = 4 };
0046 
0047     KeySequenceRecorderPrivate(KeySequenceRecorder *qq);
0048 
0049     void controlModifierlessTimeout();
0050     bool eventFilter(QObject *watched, QEvent *event) override;
0051     void handleKeyPress(QKeyEvent *event);
0052     void handleKeyRelease(QKeyEvent *event);
0053     void finishRecording();
0054     void receivedRecording();
0055 
0056     KeySequenceRecorder *q;
0057     QKeySequence m_currentKeySequence;
0058     QKeySequence m_previousKeySequence;
0059     QPointer<QWindow> m_window;
0060     bool m_isRecording;
0061     bool m_multiKeyShortcutsAllowed;
0062     bool m_modifierlessAllowed;
0063     bool m_modifierOnlyAllowed = false;
0064 
0065     Qt::KeyboardModifiers m_currentModifiers;
0066     QTimer m_modifierlessTimer;
0067     std::unique_ptr<ShortcutInhibition> m_inhibition;
0068 };
0069 
0070 constexpr Qt::KeyboardModifiers modifierMask = Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier | Qt::KeypadModifier;
0071 
0072 // Copied here from KKeyServer
0073 static bool isShiftAsModifierAllowed(int keyQt)
0074 {
0075     // remove any modifiers
0076     keyQt &= ~Qt::KeyboardModifierMask;
0077 
0078     // Shift only works as a modifier with certain keys. It's not possible
0079     // to enter the SHIFT+5 key sequence for me because this is handled as
0080     // '%' by qt on my keyboard.
0081     // The working keys are all hardcoded here :-(
0082     if (keyQt >= Qt::Key_F1 && keyQt <= Qt::Key_F35) {
0083         return true;
0084     }
0085 
0086     if (QChar(keyQt).isLetter()) {
0087         return true;
0088     }
0089 
0090     switch (keyQt) {
0091     case Qt::Key_Return:
0092     case Qt::Key_Space:
0093     case Qt::Key_Backspace:
0094     case Qt::Key_Tab:
0095     case Qt::Key_Backtab:
0096     case Qt::Key_Escape:
0097     case Qt::Key_Print:
0098     case Qt::Key_ScrollLock:
0099     case Qt::Key_Pause:
0100     case Qt::Key_PageUp:
0101     case Qt::Key_PageDown:
0102     case Qt::Key_Insert:
0103     case Qt::Key_Delete:
0104     case Qt::Key_Home:
0105     case Qt::Key_End:
0106     case Qt::Key_Up:
0107     case Qt::Key_Down:
0108     case Qt::Key_Left:
0109     case Qt::Key_Right:
0110     case Qt::Key_Enter:
0111     case Qt::Key_SysReq:
0112     case Qt::Key_CapsLock:
0113     case Qt::Key_NumLock:
0114     case Qt::Key_Help:
0115     case Qt::Key_Back:
0116     case Qt::Key_Forward:
0117     case Qt::Key_Stop:
0118     case Qt::Key_Refresh:
0119     case Qt::Key_Favorites:
0120     case Qt::Key_LaunchMedia:
0121     case Qt::Key_OpenUrl:
0122     case Qt::Key_HomePage:
0123     case Qt::Key_Search:
0124     case Qt::Key_VolumeDown:
0125     case Qt::Key_VolumeMute:
0126     case Qt::Key_VolumeUp:
0127     case Qt::Key_BassBoost:
0128     case Qt::Key_BassUp:
0129     case Qt::Key_BassDown:
0130     case Qt::Key_TrebleUp:
0131     case Qt::Key_TrebleDown:
0132     case Qt::Key_MediaPlay:
0133     case Qt::Key_MediaStop:
0134     case Qt::Key_MediaPrevious:
0135     case Qt::Key_MediaNext:
0136     case Qt::Key_MediaRecord:
0137     case Qt::Key_MediaPause:
0138     case Qt::Key_MediaTogglePlayPause:
0139     case Qt::Key_LaunchMail:
0140     case Qt::Key_Calculator:
0141     case Qt::Key_Memo:
0142     case Qt::Key_ToDoList:
0143     case Qt::Key_Calendar:
0144     case Qt::Key_PowerDown:
0145     case Qt::Key_ContrastAdjust:
0146     case Qt::Key_Standby:
0147     case Qt::Key_MonBrightnessUp:
0148     case Qt::Key_MonBrightnessDown:
0149     case Qt::Key_KeyboardLightOnOff:
0150     case Qt::Key_KeyboardBrightnessUp:
0151     case Qt::Key_KeyboardBrightnessDown:
0152     case Qt::Key_PowerOff:
0153     case Qt::Key_WakeUp:
0154     case Qt::Key_Eject:
0155     case Qt::Key_ScreenSaver:
0156     case Qt::Key_WWW:
0157     case Qt::Key_Sleep:
0158     case Qt::Key_LightBulb:
0159     case Qt::Key_Shop:
0160     case Qt::Key_History:
0161     case Qt::Key_AddFavorite:
0162     case Qt::Key_HotLinks:
0163     case Qt::Key_BrightnessAdjust:
0164     case Qt::Key_Finance:
0165     case Qt::Key_Community:
0166     case Qt::Key_AudioRewind:
0167     case Qt::Key_BackForward:
0168     case Qt::Key_ApplicationLeft:
0169     case Qt::Key_ApplicationRight:
0170     case Qt::Key_Book:
0171     case Qt::Key_CD:
0172     case Qt::Key_Clear:
0173     case Qt::Key_ClearGrab:
0174     case Qt::Key_Close:
0175     case Qt::Key_Copy:
0176     case Qt::Key_Cut:
0177     case Qt::Key_Display:
0178     case Qt::Key_DOS:
0179     case Qt::Key_Documents:
0180     case Qt::Key_Excel:
0181     case Qt::Key_Explorer:
0182     case Qt::Key_Game:
0183     case Qt::Key_Go:
0184     case Qt::Key_iTouch:
0185     case Qt::Key_LogOff:
0186     case Qt::Key_Market:
0187     case Qt::Key_Meeting:
0188     case Qt::Key_MenuKB:
0189     case Qt::Key_MenuPB:
0190     case Qt::Key_MySites:
0191     case Qt::Key_News:
0192     case Qt::Key_OfficeHome:
0193     case Qt::Key_Option:
0194     case Qt::Key_Paste:
0195     case Qt::Key_Phone:
0196     case Qt::Key_Reply:
0197     case Qt::Key_Reload:
0198     case Qt::Key_RotateWindows:
0199     case Qt::Key_RotationPB:
0200     case Qt::Key_RotationKB:
0201     case Qt::Key_Save:
0202     case Qt::Key_Send:
0203     case Qt::Key_Spell:
0204     case Qt::Key_SplitScreen:
0205     case Qt::Key_Support:
0206     case Qt::Key_TaskPane:
0207     case Qt::Key_Terminal:
0208     case Qt::Key_Tools:
0209     case Qt::Key_Travel:
0210     case Qt::Key_Video:
0211     case Qt::Key_Word:
0212     case Qt::Key_Xfer:
0213     case Qt::Key_ZoomIn:
0214     case Qt::Key_ZoomOut:
0215     case Qt::Key_Away:
0216     case Qt::Key_Messenger:
0217     case Qt::Key_WebCam:
0218     case Qt::Key_MailForward:
0219     case Qt::Key_Pictures:
0220     case Qt::Key_Music:
0221     case Qt::Key_Battery:
0222     case Qt::Key_Bluetooth:
0223     case Qt::Key_WLAN:
0224     case Qt::Key_UWB:
0225     case Qt::Key_AudioForward:
0226     case Qt::Key_AudioRepeat:
0227     case Qt::Key_AudioRandomPlay:
0228     case Qt::Key_Subtitle:
0229     case Qt::Key_AudioCycleTrack:
0230     case Qt::Key_Time:
0231     case Qt::Key_Select:
0232     case Qt::Key_View:
0233     case Qt::Key_TopMenu:
0234     case Qt::Key_Suspend:
0235     case Qt::Key_Hibernate:
0236     case Qt::Key_Launch0:
0237     case Qt::Key_Launch1:
0238     case Qt::Key_Launch2:
0239     case Qt::Key_Launch3:
0240     case Qt::Key_Launch4:
0241     case Qt::Key_Launch5:
0242     case Qt::Key_Launch6:
0243     case Qt::Key_Launch7:
0244     case Qt::Key_Launch8:
0245     case Qt::Key_Launch9:
0246     case Qt::Key_LaunchA:
0247     case Qt::Key_LaunchB:
0248     case Qt::Key_LaunchC:
0249     case Qt::Key_LaunchD:
0250     case Qt::Key_LaunchE:
0251     case Qt::Key_LaunchF:
0252         return true;
0253 
0254     default:
0255         return false;
0256     }
0257 }
0258 
0259 static bool isOkWhenModifierless(int key)
0260 {
0261     // this whole function is a hack, but especially the first line of code
0262     if (QKeySequence(key).toString().length() == 1) {
0263         return false;
0264     }
0265 
0266     switch (key) {
0267     case Qt::Key_Return:
0268     case Qt::Key_Space:
0269     case Qt::Key_Tab:
0270     case Qt::Key_Backtab: // does this ever happen?
0271     case Qt::Key_Backspace:
0272     case Qt::Key_Delete:
0273         return false;
0274     default:
0275         return true;
0276     }
0277 }
0278 
0279 static QKeySequence appendToSequence(const QKeySequence &sequence, int key)
0280 {
0281     if (sequence.count() >= KeySequenceRecorderPrivate::MaxKeyCount) {
0282         qCWarning(KGUIADDONS_LOG) << "Cannot append to a key to a sequence which is already of length" << sequence.count();
0283         return sequence;
0284     }
0285 
0286     std::array<int, KeySequenceRecorderPrivate::MaxKeyCount> keys{sequence[0], sequence[1], sequence[2], sequence[3]};
0287     keys[sequence.count()] = key;
0288     return QKeySequence(keys[0], keys[1], keys[2], keys[3]);
0289 }
0290 
0291 KeySequenceRecorderPrivate::KeySequenceRecorderPrivate(KeySequenceRecorder *qq)
0292     : QObject(qq)
0293     , q(qq)
0294 {
0295 }
0296 
0297 void KeySequenceRecorderPrivate::controlModifierlessTimeout()
0298 {
0299     if (m_currentKeySequence != 0 && !m_currentModifiers) {
0300         // No modifier key pressed currently. Start the timeout
0301         m_modifierlessTimer.start(600);
0302     } else {
0303         // A modifier is pressed. Stop the timeout
0304         m_modifierlessTimer.stop();
0305     }
0306 }
0307 
0308 bool KeySequenceRecorderPrivate::eventFilter(QObject *watched, QEvent *event)
0309 {
0310     if (!m_isRecording) {
0311         return QObject::eventFilter(watched, event);
0312     }
0313 
0314     if (event->type() == QEvent::ShortcutOverride || event->type() == QEvent::ContextMenu) {
0315         event->accept();
0316         return true;
0317     }
0318     if (event->type() == QEvent::KeyRelease) {
0319         handleKeyRelease(static_cast<QKeyEvent *>(event));
0320         return true;
0321     }
0322     if (event->type() == QEvent::KeyPress) {
0323         handleKeyPress(static_cast<QKeyEvent *>(event));
0324         return true;
0325     }
0326     return QObject::eventFilter(watched, event);
0327 }
0328 
0329 void KeySequenceRecorderPrivate::handleKeyPress(QKeyEvent *event)
0330 {
0331     m_currentModifiers = event->modifiers() & modifierMask;
0332     int key = event->key();
0333     switch (key) {
0334     case -1:
0335         qCWarning(KGUIADDONS_LOG) << "Got unknown key";
0336         // Old behavior was to stop recording here instead of continuing like this
0337         return;
0338     case 0:
0339         break;
0340     case Qt::Key_AltGr:
0341         // or else we get unicode salad
0342         break;
0343     case Qt::Key_Super_L:
0344     case Qt::Key_Super_R:
0345         // Qt doesn't properly recognize Super_L/Super_R as MetaModifier
0346         m_currentModifiers |= Qt::MetaModifier;
0347         Q_FALLTHROUGH();
0348     case Qt::Key_Shift:
0349     case Qt::Key_Control:
0350     case Qt::Key_Alt:
0351     case Qt::Key_Meta:
0352         controlModifierlessTimeout();
0353         Q_EMIT q->currentKeySequenceChanged();
0354         break;
0355     default:
0356         if (m_currentKeySequence.count() == 0 && !(m_currentModifiers & ~Qt::ShiftModifier)) {
0357             // It's the first key and no modifier pressed. Check if this is allowed
0358             if (!(isOkWhenModifierless(key) || m_modifierlessAllowed)) {
0359                 // No it's not
0360                 return;
0361             }
0362         }
0363 
0364         // We now have a valid key press.
0365         if ((key == Qt::Key_Backtab) && (m_currentModifiers & Qt::ShiftModifier)) {
0366             key = Qt::Key_Tab | m_currentModifiers;
0367         } else if (isShiftAsModifierAllowed(key)) {
0368             key |= m_currentModifiers;
0369         } else {
0370             key |= (m_currentModifiers & ~Qt::ShiftModifier);
0371         }
0372 
0373         m_currentKeySequence = appendToSequence(m_currentKeySequence, key);
0374         Q_EMIT q->currentKeySequenceChanged();
0375         // Now we are in a critical region (race), where recording is still
0376         // ongoing, but key sequence has already changed (potentially) to the
0377         // longest. But we still want currentKeySequenceChanged to trigger
0378         // before gotKeySequence, so there's only so much we can do about it.
0379         if ((!m_multiKeyShortcutsAllowed) || (m_currentKeySequence.count() == MaxKeyCount)) {
0380             finishRecording();
0381             break;
0382         }
0383         controlModifierlessTimeout();
0384     }
0385     event->accept();
0386 }
0387 
0388 void KeySequenceRecorderPrivate::handleKeyRelease(QKeyEvent *event)
0389 {
0390     Qt::KeyboardModifiers modifiers = event->modifiers() & modifierMask;
0391 
0392     /* The modifier release event (e.g. Qt::Key_Shift) also has the modifier
0393        flag set so we were interpreting the "Shift" press as "Shift + Shift".
0394        This function makes it so we just take the key part but not the modifier
0395        if we are doing this one alone. */
0396     const auto justKey = [&](Qt::KeyboardModifiers modifier) {
0397         modifiers &= ~modifier;
0398         if (m_currentKeySequence.isEmpty() && m_modifierOnlyAllowed) {
0399             m_currentKeySequence = appendToSequence(m_currentKeySequence, event->key());
0400         }
0401     };
0402     switch (event->key()) {
0403     case -1:
0404         return;
0405     case Qt::Key_Super_L:
0406     case Qt::Key_Super_R:
0407     case Qt::Key_Meta:
0408         justKey(Qt::MetaModifier);
0409         break;
0410     case Qt::Key_Shift:
0411         justKey(Qt::ShiftModifier);
0412         break;
0413     case Qt::Key_Control:
0414         justKey(Qt::ControlModifier);
0415         break;
0416     case Qt::Key_Alt:
0417         justKey(Qt::AltModifier);
0418         break;
0419     }
0420 
0421     if ((modifiers & m_currentModifiers) < m_currentModifiers) {
0422         m_currentModifiers = modifiers;
0423         controlModifierlessTimeout();
0424         Q_EMIT q->currentKeySequenceChanged();
0425     }
0426 }
0427 
0428 void KeySequenceRecorderPrivate::receivedRecording()
0429 {
0430     m_modifierlessTimer.stop();
0431     m_isRecording = false;
0432     m_currentModifiers = Qt::NoModifier;
0433     if (m_inhibition) {
0434         m_inhibition->disableInhibition();
0435     }
0436     QObject::disconnect(KeySequenceRecorderGlobal::self(), &KeySequenceRecorderGlobal::sequenceRecordingStarted,
0437                         q, &KeySequenceRecorder::cancelRecording);
0438     Q_EMIT q->recordingChanged();
0439 }
0440 
0441 void KeySequenceRecorderPrivate::finishRecording()
0442 {
0443     receivedRecording();
0444     Q_EMIT q->gotKeySequence(m_currentKeySequence);
0445 }
0446 
0447 KeySequenceRecorder::KeySequenceRecorder(QWindow *window, QObject *parent)
0448     : QObject(parent)
0449     , d(new KeySequenceRecorderPrivate(this))
0450 {
0451     d->m_isRecording = false;
0452     d->m_modifierlessAllowed = false;
0453     d->m_multiKeyShortcutsAllowed = true;
0454 
0455     setWindow(window);
0456     connect(&d->m_modifierlessTimer, &QTimer::timeout, d.get(), &KeySequenceRecorderPrivate::finishRecording);
0457 }
0458 
0459 KeySequenceRecorder::~KeySequenceRecorder() noexcept
0460 {
0461     if (d->m_inhibition && d->m_inhibition->shortcutsAreInhibited()) {
0462         d->m_inhibition->disableInhibition();
0463     }
0464 }
0465 
0466 void KeySequenceRecorder::startRecording()
0467 {
0468     d->m_previousKeySequence = d->m_currentKeySequence;
0469 
0470     KeySequenceRecorderGlobal::self()->sequenceRecordingStarted();
0471     connect(KeySequenceRecorderGlobal::self(),
0472             &KeySequenceRecorderGlobal::sequenceRecordingStarted,
0473             this,
0474             &KeySequenceRecorder::cancelRecording,
0475             Qt::UniqueConnection);
0476 
0477     if (!d->m_window) {
0478         qCWarning(KGUIADDONS_LOG) << "Cannot record without a window";
0479         return;
0480     }
0481     d->m_isRecording = true;
0482     d->m_currentKeySequence = QKeySequence();
0483     if (d->m_inhibition) {
0484         d->m_inhibition->enableInhibition();
0485     }
0486     Q_EMIT recordingChanged();
0487     Q_EMIT currentKeySequenceChanged();
0488 }
0489 
0490 void KeySequenceRecorder::cancelRecording()
0491 {
0492     setCurrentKeySequence(d->m_previousKeySequence);
0493     d->receivedRecording();
0494     Q_ASSERT(!isRecording());
0495 }
0496 
0497 bool KeySequenceRecorder::isRecording() const
0498 {
0499     return d->m_isRecording;
0500 }
0501 
0502 QKeySequence KeySequenceRecorder::currentKeySequence() const
0503 {
0504     // We need a check for count() here because there's a race between the
0505     // state of recording and a length of currentKeySequence.
0506     if (d->m_isRecording && d->m_currentKeySequence.count() < KeySequenceRecorderPrivate::MaxKeyCount) {
0507         return appendToSequence(d->m_currentKeySequence, d->m_currentModifiers);
0508     } else {
0509         return d->m_currentKeySequence;
0510     }
0511 }
0512 
0513 void KeySequenceRecorder::setCurrentKeySequence(const QKeySequence &sequence)
0514 {
0515     if (d->m_currentKeySequence == sequence) {
0516         return;
0517     }
0518     d->m_currentKeySequence = sequence;
0519     Q_EMIT currentKeySequenceChanged();
0520 }
0521 
0522 QWindow *KeySequenceRecorder::window() const
0523 {
0524     return d->m_window;
0525 }
0526 
0527 void KeySequenceRecorder::setWindow(QWindow *window)
0528 {
0529     if (window == d->m_window) {
0530         return;
0531     }
0532 
0533     if (d->m_window) {
0534         d->m_window->removeEventFilter(d.get());
0535     }
0536 
0537     if (window) {
0538         window->installEventFilter(d.get());
0539         qCDebug(KGUIADDONS_LOG) << "listening for events in" << window;
0540     }
0541 
0542     if (qGuiApp->platformName() == QLatin1String("wayland")) {
0543 #ifdef WITH_WAYLAND
0544         d->m_inhibition.reset(new WaylandInhibition(window));
0545 #endif
0546     } else {
0547         d->m_inhibition.reset(new KeyboardGrabber(window));
0548     }
0549 
0550     d->m_window = window;
0551 
0552     Q_EMIT windowChanged();
0553 }
0554 
0555 bool KeySequenceRecorder::multiKeyShortcutsAllowed() const
0556 {
0557     return d->m_multiKeyShortcutsAllowed;
0558 }
0559 
0560 void KeySequenceRecorder::setMultiKeyShortcutsAllowed(bool allowed)
0561 {
0562     if (allowed == d->m_multiKeyShortcutsAllowed) {
0563         return;
0564     }
0565     d->m_multiKeyShortcutsAllowed = allowed;
0566     Q_EMIT multiKeyShortcutsAllowedChanged();
0567 }
0568 
0569 bool KeySequenceRecorder::modifierlessAllowed() const
0570 {
0571     return d->m_modifierlessAllowed;
0572 }
0573 
0574 void KeySequenceRecorder::setModifierlessAllowed(bool allowed)
0575 {
0576     if (allowed == d->m_modifierlessAllowed) {
0577         return;
0578     }
0579     d->m_modifierlessAllowed = allowed;
0580     Q_EMIT modifierlessAllowedChanged();
0581 }
0582 
0583 bool KeySequenceRecorder::modifierOnlyAllowed() const
0584 {
0585     return d->m_modifierOnlyAllowed;
0586 }
0587 
0588 void KeySequenceRecorder::setModifierOnlyAllowed(bool allowed)
0589 {
0590     if (allowed == d->m_modifierOnlyAllowed) {
0591         return;
0592     }
0593     d->m_modifierOnlyAllowed = allowed;
0594     Q_EMIT modifierOnlyAllowedChanged();
0595 }
0596 
0597 #include "keysequencerecorder.moc"
0598 #include "moc_keysequencerecorder.cpp"