10#include "kkeysequencerecorder.h"
12#include "keyboardgrabber_p.h"
13#include "kguiaddons_debug.h"
14#include "shortcutinhibition_p.h"
15#include "waylandinhibition_p.h"
17#include <QGuiApplication>
27class KKeySequenceRecorderGlobal :
public QObject
31 static KKeySequenceRecorderGlobal *self()
33 static KKeySequenceRecorderGlobal s_self;
38 void sequenceRecordingStarted();
41class KKeySequenceRecorderPrivate :
public QObject
46 enum { MaxKeyCount = 4 };
48 KKeySequenceRecorderPrivate(KKeySequenceRecorder *qq);
50 bool isKeyCombinationAccepted(QKeyCombination keyCombination)
const;
52 void controlModifierlessTimeout();
53 bool eventFilter(
QObject *watched, QEvent *
event)
override;
54 void handleKeyPress(QKeyEvent *
event);
55 void handleKeyRelease(QKeyEvent *
event);
56 void finishRecording();
57 void receivedRecording();
59 KKeySequenceRecorder *q;
60 QKeySequence m_currentKeySequence;
61 QKeySequence m_previousKeySequence;
62 QPointer<QWindow> m_window;
63 KKeySequenceRecorder::Patterns m_patterns;
65 bool m_multiKeyShortcutsAllowed;
68 QTimer m_modifierlessTimer;
69 std::unique_ptr<ShortcutInhibition> m_inhibition;
72 bool m_isReleasingModifierOnly =
false;
73 std::chrono::nanoseconds m_modifierFirstReleaseTime;
79static bool isShiftAsModifierAllowed(
int keyQt)
82 keyQt &=
~Qt::KeyboardModifierMask;
271static bool isOkWhenModifierless(
int key)
291bool KKeySequenceRecorderPrivate::isKeyCombinationAccepted(
QKeyCombination keyCombination)
const
294 const bool inputIncludesKey = keyCombination.
key();
302 for (
const auto &pattern : basePatterns) {
303 if (!(m_patterns & pattern)) {
310 if (inputIncludesModifiers != patternIncludesModifiers) {
312 if (!(patternIncludesModifiers && patternIncludesKey && isOkWhenModifierless(keyCombination.
key()))) {
317 if (inputIncludesKey == patternIncludesKey) {
327 if (sequence.
count() >= KKeySequenceRecorderPrivate::MaxKeyCount) {
328 qCWarning(KGUIADDONS_LOG) <<
"Cannot append to a key to a sequence which is already of length" << sequence.
count();
332 std::array<int, KKeySequenceRecorderPrivate::MaxKeyCount> keys{sequence[0].toCombined(),
333 sequence[1].toCombined(),
334 sequence[2].toCombined(),
335 sequence[3].toCombined()};
343 keys[sequence.
count() - 1] = key;
344 return QKeySequence(keys[0], keys[1], keys[2], keys[3]);
347 keys[sequence.
count()] = key;
348 return QKeySequence(keys[0], keys[1], keys[2], keys[3]);
357void KKeySequenceRecorderPrivate::controlModifierlessTimeout()
359 if (m_currentKeySequence != 0 && !m_currentModifiers) {
361 m_modifierlessTimer.start(600);
364 m_modifierlessTimer.stop();
368bool KKeySequenceRecorderPrivate::eventFilter(
QObject *watched,
QEvent *event)
370 if (!m_isRecording) {
379 handleKeyRelease(
static_cast<QKeyEvent *
>(
event));
383 handleKeyPress(
static_cast<QKeyEvent *
>(
event));
408void KKeySequenceRecorderPrivate::handleKeyPress(
QKeyEvent *event)
410 m_isReleasingModifierOnly =
false;
411 m_currentModifiers =
event->modifiers() & modifierMask;
412 const int key =
event->key();
415 qCWarning(KGUIADDONS_LOG) <<
"Got unknown key";
429 m_currentModifiers |= keyToModifier(key);
430 m_lastPressedModifiers = m_currentModifiers;
431 controlModifierlessTimeout();
432 Q_EMIT q->currentKeySequenceChanged();
437 QKeyCombination keyCombination;
439 keyCombination = QKeyCombination(m_currentModifiers,
Qt::Key_Tab);
441 keyCombination = QKeyCombination(m_currentModifiers,
Qt::Key(key));
446 if (!isKeyCombinationAccepted(keyCombination)) {
450 m_currentKeySequence = appendToSequence(m_currentKeySequence, keyCombination.
toCombined());
451 Q_EMIT q->currentKeySequenceChanged();
456 if ((!m_multiKeyShortcutsAllowed) || (m_currentKeySequence.count() == MaxKeyCount)) {
460 controlModifierlessTimeout();
483void KKeySequenceRecorderPrivate::handleKeyRelease(
QKeyEvent *event)
487 switch (
event->key()) {
496 modifiers &= ~keyToModifier(
event->key());
498 if ((modifiers & m_currentModifiers) < m_currentModifiers) {
499 constexpr auto releaseTimeout = std::chrono::milliseconds(200);
500 const auto currentTime = std::chrono::steady_clock::now().time_since_epoch();
501 if (!m_isReleasingModifierOnly) {
502 m_isReleasingModifierOnly =
true;
503 m_modifierFirstReleaseTime = currentTime;
505 if (!modifiers && (currentTime - m_modifierFirstReleaseTime) < releaseTimeout) {
506 const auto keyCombination = QKeyCombination(m_lastPressedModifiers,
Qt::Key(0));
507 if (isKeyCombinationAccepted(keyCombination)) {
508 m_currentKeySequence = appendToSequence(m_currentKeySequence, prettifyModifierOnly(keyCombination).toCombined());
512 m_currentModifiers = modifiers;
513 Q_EMIT q->currentKeySequenceChanged();
514 if (m_currentKeySequence.count() == (m_multiKeyShortcutsAllowed ? MaxKeyCount : 1)) {
517 controlModifierlessTimeout();
521void KKeySequenceRecorderPrivate::receivedRecording()
523 m_modifierlessTimer.stop();
524 m_isRecording =
false;
527 m_isReleasingModifierOnly =
false;
529 m_inhibition->disableInhibition();
532 Q_EMIT q->recordingChanged();
535void KKeySequenceRecorderPrivate::finishRecording()
538 Q_EMIT q->gotKeySequence(m_currentKeySequence);
543 , d(new KKeySequenceRecorderPrivate(this))
545 d->m_isRecording =
false;
547 d->m_multiKeyShortcutsAllowed =
true;
550 connect(&d->m_modifierlessTimer, &
QTimer::timeout, d.get(), &KKeySequenceRecorderPrivate::finishRecording);
553KKeySequenceRecorder::~KKeySequenceRecorder() noexcept
555 if (d->m_inhibition && d->m_inhibition->shortcutsAreInhibited()) {
556 d->m_inhibition->disableInhibition();
562 d->m_previousKeySequence = d->m_currentKeySequence;
564 KKeySequenceRecorderGlobal::self()->sequenceRecordingStarted();
565 connect(KKeySequenceRecorderGlobal::self(),
566 &KKeySequenceRecorderGlobal::sequenceRecordingStarted,
572 qCWarning(KGUIADDONS_LOG) <<
"Cannot record without a window";
575 d->m_isRecording =
true;
577 if (d->m_inhibition) {
578 d->m_inhibition->enableInhibition();
580 Q_EMIT recordingChanged();
581 Q_EMIT currentKeySequenceChanged();
586 setCurrentKeySequence(d->m_previousKeySequence);
587 d->receivedRecording();
593 return d->m_isRecording;
600 if (d->m_isRecording && d->m_currentKeySequence.count() < KKeySequenceRecorderPrivate::MaxKeyCount) {
601 return appendToSequence(d->m_currentKeySequence, d->m_currentModifiers);
603 return d->m_currentKeySequence;
607void KKeySequenceRecorder::setCurrentKeySequence(
const QKeySequence &sequence)
609 if (d->m_currentKeySequence == sequence) {
612 d->m_currentKeySequence = sequence;
613 Q_EMIT currentKeySequenceChanged();
621void KKeySequenceRecorder::setWindow(
QWindow *window)
623 if (
window == d->m_window) {
628 d->m_window->removeEventFilter(d.get());
632 window->installEventFilter(d.get());
633 qCDebug(KGUIADDONS_LOG) <<
"listening for events in" <<
window;
636 if (qGuiApp->platformName() == QLatin1String(
"wayland")) {
638 d->m_inhibition.reset(
new WaylandInhibition(
window));
641 d->m_inhibition.reset(
new KeyboardGrabber(
window));
651 return d->m_multiKeyShortcutsAllowed;
654void KKeySequenceRecorder::setMultiKeyShortcutsAllowed(
bool allowed)
656 if (allowed == d->m_multiKeyShortcutsAllowed) {
659 d->m_multiKeyShortcutsAllowed = allowed;
660 Q_EMIT multiKeyShortcutsAllowedChanged();
663#if KGUIADDONS_BUILD_DEPRECATED_SINCE(6, 12)
666 return d->m_patterns &
Key;
669void KKeySequenceRecorder::setModifierlessAllowed(
bool allowed)
672 setPatterns(d->m_patterns |
Key);
674 setPatterns(d->m_patterns & ~
Key);
683void KKeySequenceRecorder::setModifierOnlyAllowed(
bool allowed)
686 setPatterns(d->m_patterns |
Modifier);
688 setPatterns(d->m_patterns & ~
Modifier);
693void KKeySequenceRecorder::setPatterns(Patterns patterns)
703#if KGUIADDONS_BUILD_DEPRECATED_SINCE(6, 12)
711#if KGUIADDONS_BUILD_DEPRECATED_SINCE(6, 12)
713 Q_EMIT modifierlessAllowedChanged();
716 Q_EMIT modifierOnlyAllowedChanged();
723 return d->m_patterns;
726#include "kkeysequencerecorder.moc"
727#include "moc_kkeysequencerecorder.cpp"
Record a QKeySequence by listening to key events in a window.
Patterns patterns
Specifies what components the recorded shortcut must have, for example whether the shortcut must cont...
Q_INVOKABLE void startRecording()
Start recording.
bool multiKeyShortcutsAllowed
Controls the amount of key combinations that are captured until recording stops and gotKeySequence is...
bool modifierOnlyAllowed
It makes it acceptable for the key sequence to be just a modifier (e.g.
KKeySequenceRecorder(QWindow *window, QObject *parent=nullptr)
Constructor.
QKeySequence currentKeySequence
The recorded key sequence.
bool isRecording
Whether key events are currently recorded.
QWindow * window
The window in which the key events are happening that should be recorded.
void cancelRecording()
Stops the recording session.
Pattern
The Pattern type specifies what components the recorded shortcut must have, e.g.
@ Modifier
The recorded shortcut must contain one or more modifier keys (Meta, Shift, Ctrl, or Alt).
@ Key
The recorded shortcut must contain only one regular key, e.g.
@ ModifierAndKey
The recorded shortcut must contain one or more modifier keys followed by a regular key,...
bool modifierlessAllowed
If key presses of "plain" keys without a modifier are considered to be a valid finished key combinati...
char * toString(const EngineQuery &query)
bool isShiftAsModifierAllowed(int keyQt)
bool isLetter(char32_t ucs4)
Qt::Key key() const const
Qt::KeyboardModifiers keyboardModifiers() const const
int toCombined() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
virtual bool event(QEvent *e)
virtual bool eventFilter(QObject *watched, QEvent *event)
QObject * parent() const const
typedef KeyboardModifiers