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"