File indexing completed on 2024-04-28 03:54:16

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 "kkeysequencerecorder.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 KKeySequenceRecorderGlobal : public QObject
0027 {
0028     Q_OBJECT
0029 public:
0030     static KKeySequenceRecorderGlobal *self()
0031     {
0032         static KKeySequenceRecorderGlobal s_self;
0033         return &s_self;
0034     }
0035 
0036 Q_SIGNALS:
0037     void sequenceRecordingStarted();
0038 };
0039 
0040 class KKeySequenceRecorderPrivate : public QObject
0041 {
0042     Q_OBJECT
0043 public:
0044     // Copy of QKeySequencePrivate::MaxKeyCount from private header
0045     enum { MaxKeyCount = 4 };
0046 
0047     KKeySequenceRecorderPrivate(KKeySequenceRecorder *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     KKeySequenceRecorder *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::isLetter(keyQt)) {
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() >= KKeySequenceRecorderPrivate::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, KKeySequenceRecorderPrivate::MaxKeyCount> keys{sequence[0].toCombined(),
0287                                                                    sequence[1].toCombined(),
0288                                                                    sequence[2].toCombined(),
0289                                                                    sequence[3].toCombined()};
0290     // When the user presses Mod(s)+Alt+Print, the SysReq event is fired only
0291     // when the Alt key is released. Before we get the Mod(s)+SysReq event, we
0292     // first get a Mod(s)+Alt event, which we have to ignore.
0293     // Known limitation: only works when the Alt key is released before the Mod(s) key(s).
0294     if ((key & ~Qt::KeyboardModifierMask) == Qt::Key_SysReq) {
0295         key = Qt::Key_Print | (key & Qt::KeyboardModifierMask) | Qt::AltModifier;
0296         if (sequence.count() > 0 && (sequence[sequence.count() - 1].toCombined() & ~Qt::KeyboardModifierMask) == Qt::Key_Alt) {
0297             keys[sequence.count() - 1] = key;
0298             return QKeySequence(keys[0], keys[1], keys[2], keys[3]);
0299         }
0300     }
0301     keys[sequence.count()] = key;
0302     return QKeySequence(keys[0], keys[1], keys[2], keys[3]);
0303 }
0304 
0305 KKeySequenceRecorderPrivate::KKeySequenceRecorderPrivate(KKeySequenceRecorder *qq)
0306     : QObject(qq)
0307     , q(qq)
0308 {
0309 }
0310 
0311 void KKeySequenceRecorderPrivate::controlModifierlessTimeout()
0312 {
0313     if (m_currentKeySequence != 0 && !m_currentModifiers) {
0314         // No modifier key pressed currently. Start the timeout
0315         m_modifierlessTimer.start(600);
0316     } else {
0317         // A modifier is pressed. Stop the timeout
0318         m_modifierlessTimer.stop();
0319     }
0320 }
0321 
0322 bool KKeySequenceRecorderPrivate::eventFilter(QObject *watched, QEvent *event)
0323 {
0324     if (!m_isRecording) {
0325         return QObject::eventFilter(watched, event);
0326     }
0327 
0328     if (event->type() == QEvent::ShortcutOverride || event->type() == QEvent::ContextMenu) {
0329         event->accept();
0330         return true;
0331     }
0332     if (event->type() == QEvent::KeyRelease) {
0333         handleKeyRelease(static_cast<QKeyEvent *>(event));
0334         return true;
0335     }
0336     if (event->type() == QEvent::KeyPress) {
0337         handleKeyPress(static_cast<QKeyEvent *>(event));
0338         return true;
0339     }
0340     return QObject::eventFilter(watched, event);
0341 }
0342 
0343 void KKeySequenceRecorderPrivate::handleKeyPress(QKeyEvent *event)
0344 {
0345     m_currentModifiers = event->modifiers() & modifierMask;
0346     int key = event->key();
0347     switch (key) {
0348     case -1:
0349         qCWarning(KGUIADDONS_LOG) << "Got unknown key";
0350         // Old behavior was to stop recording here instead of continuing like this
0351         return;
0352     case 0:
0353         break;
0354     case Qt::Key_AltGr:
0355         // or else we get unicode salad
0356         break;
0357     case Qt::Key_Super_L:
0358     case Qt::Key_Super_R:
0359         // Qt doesn't properly recognize Super_L/Super_R as MetaModifier
0360         m_currentModifiers |= Qt::MetaModifier;
0361         Q_FALLTHROUGH();
0362     case Qt::Key_Shift:
0363     case Qt::Key_Control:
0364     case Qt::Key_Alt:
0365     case Qt::Key_Meta:
0366         controlModifierlessTimeout();
0367         Q_EMIT q->currentKeySequenceChanged();
0368         break;
0369     default:
0370         if (m_currentKeySequence.count() == 0 && !(m_currentModifiers & ~Qt::ShiftModifier)) {
0371             // It's the first key and no modifier pressed. Check if this is allowed
0372             if (!(isOkWhenModifierless(key) || m_modifierlessAllowed)) {
0373                 // No it's not
0374                 return;
0375             }
0376         }
0377 
0378         // We now have a valid key press.
0379         if ((key == Qt::Key_Backtab) && (m_currentModifiers & Qt::ShiftModifier)) {
0380             key = QKeyCombination(Qt::Key_Tab).toCombined() | m_currentModifiers;
0381         } else if (isShiftAsModifierAllowed(key)) {
0382             key |= m_currentModifiers;
0383         } else {
0384             key |= (m_currentModifiers & ~Qt::ShiftModifier);
0385         }
0386 
0387         m_currentKeySequence = appendToSequence(m_currentKeySequence, key);
0388         Q_EMIT q->currentKeySequenceChanged();
0389         // Now we are in a critical region (race), where recording is still
0390         // ongoing, but key sequence has already changed (potentially) to the
0391         // longest. But we still want currentKeySequenceChanged to trigger
0392         // before gotKeySequence, so there's only so much we can do about it.
0393         if ((!m_multiKeyShortcutsAllowed) || (m_currentKeySequence.count() == MaxKeyCount)) {
0394             finishRecording();
0395             break;
0396         }
0397         controlModifierlessTimeout();
0398     }
0399     event->accept();
0400 }
0401 
0402 void KKeySequenceRecorderPrivate::handleKeyRelease(QKeyEvent *event)
0403 {
0404     Qt::KeyboardModifiers modifiers = event->modifiers() & modifierMask;
0405 
0406     /* The modifier release event (e.g. Qt::Key_Shift) also has the modifier
0407        flag set so we were interpreting the "Shift" press as "Shift + Shift".
0408        This function makes it so we just take the key part but not the modifier
0409        if we are doing this one alone. */
0410     const auto justKey = [&](Qt::KeyboardModifiers modifier) {
0411         modifiers &= ~modifier;
0412         if (m_currentKeySequence.isEmpty() && m_modifierOnlyAllowed) {
0413             m_currentKeySequence = appendToSequence(m_currentKeySequence, event->key());
0414         }
0415     };
0416     switch (event->key()) {
0417     case -1:
0418         return;
0419     case Qt::Key_Super_L:
0420     case Qt::Key_Super_R:
0421     case Qt::Key_Meta:
0422         justKey(Qt::MetaModifier);
0423         break;
0424     case Qt::Key_Shift:
0425         justKey(Qt::ShiftModifier);
0426         break;
0427     case Qt::Key_Control:
0428         justKey(Qt::ControlModifier);
0429         break;
0430     case Qt::Key_Alt:
0431         justKey(Qt::AltModifier);
0432         break;
0433     }
0434 
0435     if ((modifiers & m_currentModifiers) < m_currentModifiers) {
0436         m_currentModifiers = modifiers;
0437         controlModifierlessTimeout();
0438         Q_EMIT q->currentKeySequenceChanged();
0439     }
0440 }
0441 
0442 void KKeySequenceRecorderPrivate::receivedRecording()
0443 {
0444     m_modifierlessTimer.stop();
0445     m_isRecording = false;
0446     m_currentModifiers = Qt::NoModifier;
0447     if (m_inhibition) {
0448         m_inhibition->disableInhibition();
0449     }
0450     QObject::disconnect(KKeySequenceRecorderGlobal::self(), &KKeySequenceRecorderGlobal::sequenceRecordingStarted, q, &KKeySequenceRecorder::cancelRecording);
0451     Q_EMIT q->recordingChanged();
0452 }
0453 
0454 void KKeySequenceRecorderPrivate::finishRecording()
0455 {
0456     receivedRecording();
0457     Q_EMIT q->gotKeySequence(m_currentKeySequence);
0458 }
0459 
0460 KKeySequenceRecorder::KKeySequenceRecorder(QWindow *window, QObject *parent)
0461     : QObject(parent)
0462     , d(new KKeySequenceRecorderPrivate(this))
0463 {
0464     d->m_isRecording = false;
0465     d->m_modifierlessAllowed = false;
0466     d->m_multiKeyShortcutsAllowed = true;
0467 
0468     setWindow(window);
0469     connect(&d->m_modifierlessTimer, &QTimer::timeout, d.get(), &KKeySequenceRecorderPrivate::finishRecording);
0470 }
0471 
0472 KKeySequenceRecorder::~KKeySequenceRecorder() noexcept
0473 {
0474     if (d->m_inhibition && d->m_inhibition->shortcutsAreInhibited()) {
0475         d->m_inhibition->disableInhibition();
0476     }
0477 }
0478 
0479 void KKeySequenceRecorder::startRecording()
0480 {
0481     d->m_previousKeySequence = d->m_currentKeySequence;
0482 
0483     KKeySequenceRecorderGlobal::self()->sequenceRecordingStarted();
0484     connect(KKeySequenceRecorderGlobal::self(),
0485             &KKeySequenceRecorderGlobal::sequenceRecordingStarted,
0486             this,
0487             &KKeySequenceRecorder::cancelRecording,
0488             Qt::UniqueConnection);
0489 
0490     if (!d->m_window) {
0491         qCWarning(KGUIADDONS_LOG) << "Cannot record without a window";
0492         return;
0493     }
0494     d->m_isRecording = true;
0495     d->m_currentKeySequence = QKeySequence();
0496     if (d->m_inhibition) {
0497         d->m_inhibition->enableInhibition();
0498     }
0499     Q_EMIT recordingChanged();
0500     Q_EMIT currentKeySequenceChanged();
0501 }
0502 
0503 void KKeySequenceRecorder::cancelRecording()
0504 {
0505     setCurrentKeySequence(d->m_previousKeySequence);
0506     d->receivedRecording();
0507     Q_ASSERT(!isRecording());
0508 }
0509 
0510 bool KKeySequenceRecorder::isRecording() const
0511 {
0512     return d->m_isRecording;
0513 }
0514 
0515 QKeySequence KKeySequenceRecorder::currentKeySequence() const
0516 {
0517     // We need a check for count() here because there's a race between the
0518     // state of recording and a length of currentKeySequence.
0519     if (d->m_isRecording && d->m_currentKeySequence.count() < KKeySequenceRecorderPrivate::MaxKeyCount) {
0520         return appendToSequence(d->m_currentKeySequence, d->m_currentModifiers);
0521     } else {
0522         return d->m_currentKeySequence;
0523     }
0524 }
0525 
0526 void KKeySequenceRecorder::setCurrentKeySequence(const QKeySequence &sequence)
0527 {
0528     if (d->m_currentKeySequence == sequence) {
0529         return;
0530     }
0531     d->m_currentKeySequence = sequence;
0532     Q_EMIT currentKeySequenceChanged();
0533 }
0534 
0535 QWindow *KKeySequenceRecorder::window() const
0536 {
0537     return d->m_window;
0538 }
0539 
0540 void KKeySequenceRecorder::setWindow(QWindow *window)
0541 {
0542     if (window == d->m_window) {
0543         return;
0544     }
0545 
0546     if (d->m_window) {
0547         d->m_window->removeEventFilter(d.get());
0548     }
0549 
0550     if (window) {
0551         window->installEventFilter(d.get());
0552         qCDebug(KGUIADDONS_LOG) << "listening for events in" << window;
0553     }
0554 
0555     if (qGuiApp->platformName() == QLatin1String("wayland")) {
0556 #ifdef WITH_WAYLAND
0557         d->m_inhibition.reset(new WaylandInhibition(window));
0558 #endif
0559     } else {
0560         d->m_inhibition.reset(new KeyboardGrabber(window));
0561     }
0562 
0563     d->m_window = window;
0564 
0565     Q_EMIT windowChanged();
0566 }
0567 
0568 bool KKeySequenceRecorder::multiKeyShortcutsAllowed() const
0569 {
0570     return d->m_multiKeyShortcutsAllowed;
0571 }
0572 
0573 void KKeySequenceRecorder::setMultiKeyShortcutsAllowed(bool allowed)
0574 {
0575     if (allowed == d->m_multiKeyShortcutsAllowed) {
0576         return;
0577     }
0578     d->m_multiKeyShortcutsAllowed = allowed;
0579     Q_EMIT multiKeyShortcutsAllowedChanged();
0580 }
0581 
0582 bool KKeySequenceRecorder::modifierlessAllowed() const
0583 {
0584     return d->m_modifierlessAllowed;
0585 }
0586 
0587 void KKeySequenceRecorder::setModifierlessAllowed(bool allowed)
0588 {
0589     if (allowed == d->m_modifierlessAllowed) {
0590         return;
0591     }
0592     d->m_modifierlessAllowed = allowed;
0593     Q_EMIT modifierlessAllowedChanged();
0594 }
0595 
0596 bool KKeySequenceRecorder::modifierOnlyAllowed() const
0597 {
0598     return d->m_modifierOnlyAllowed;
0599 }
0600 
0601 void KKeySequenceRecorder::setModifierOnlyAllowed(bool allowed)
0602 {
0603     if (allowed == d->m_modifierOnlyAllowed) {
0604         return;
0605     }
0606     d->m_modifierOnlyAllowed = allowed;
0607     Q_EMIT modifierOnlyAllowedChanged();
0608 }
0609 
0610 #include "kkeysequencerecorder.moc"
0611 #include "moc_kkeysequencerecorder.cpp"