File indexing completed on 2024-05-05 07:55:27
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"