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 void controlModifierlessTimeout();
51 bool eventFilter(
QObject *watched, QEvent *
event)
override;
52 void handleKeyPress(QKeyEvent *
event);
53 void handleKeyRelease(QKeyEvent *
event);
54 void finishRecording();
55 void receivedRecording();
57 KKeySequenceRecorder *q;
58 QKeySequence m_currentKeySequence;
59 QKeySequence m_previousKeySequence;
60 QPointer<QWindow> m_window;
62 bool m_multiKeyShortcutsAllowed;
63 bool m_modifierlessAllowed;
64 bool m_modifierOnlyAllowed =
false;
67 QTimer m_modifierlessTimer;
68 std::unique_ptr<ShortcutInhibition> m_inhibition;
71 bool m_isReleasingModifierOnly =
false;
72 std::chrono::nanoseconds m_modifierFirstReleaseTime;
78static bool isShiftAsModifierAllowed(
int keyQt)
81 keyQt &=
~Qt::KeyboardModifierMask;
270static bool isOkWhenModifierless(
int key)
292 if (sequence.
count() >= KKeySequenceRecorderPrivate::MaxKeyCount) {
293 qCWarning(KGUIADDONS_LOG) <<
"Cannot append to a key to a sequence which is already of length" << sequence.
count();
297 std::array<int, KKeySequenceRecorderPrivate::MaxKeyCount> keys{sequence[0].toCombined(),
298 sequence[1].toCombined(),
299 sequence[2].toCombined(),
300 sequence[3].toCombined()};
308 keys[sequence.
count() - 1] = key;
309 return QKeySequence(keys[0], keys[1], keys[2], keys[3]);
312 keys[sequence.
count()] = key;
313 return QKeySequence(keys[0], keys[1], keys[2], keys[3]);
322void KKeySequenceRecorderPrivate::controlModifierlessTimeout()
324 if (m_currentKeySequence != 0 && !m_currentModifiers) {
326 m_modifierlessTimer.start(600);
329 m_modifierlessTimer.stop();
333bool KKeySequenceRecorderPrivate::eventFilter(
QObject *watched,
QEvent *event)
335 if (!m_isRecording) {
344 handleKeyRelease(
static_cast<QKeyEvent *
>(
event));
348 handleKeyPress(
static_cast<QKeyEvent *
>(
event));
373void KKeySequenceRecorderPrivate::handleKeyPress(
QKeyEvent *event)
375 m_isReleasingModifierOnly =
false;
376 m_currentModifiers =
event->modifiers() & modifierMask;
377 int key =
event->key();
380 qCWarning(KGUIADDONS_LOG) <<
"Got unknown key";
394 m_currentModifiers |= keyToModifier(key);
395 m_lastPressedModifiers = m_currentModifiers;
396 controlModifierlessTimeout();
397 Q_EMIT q->currentKeySequenceChanged();
401 if (m_currentKeySequence.count() == 0 && !(m_currentModifiers & ~
Qt::ShiftModifier)) {
403 if (!(isOkWhenModifierless(key) || m_modifierlessAllowed)) {
411 key = QKeyCombination(
Qt::Key_Tab).toCombined() | m_currentModifiers;
413 key |= m_currentModifiers;
415 key |= (m_currentModifiers & ~Qt::ShiftModifier);
418 m_currentKeySequence = appendToSequence(m_currentKeySequence, key);
419 Q_EMIT q->currentKeySequenceChanged();
424 if ((!m_multiKeyShortcutsAllowed) || (m_currentKeySequence.count() == MaxKeyCount)) {
428 controlModifierlessTimeout();
450void KKeySequenceRecorderPrivate::handleKeyRelease(
QKeyEvent *event)
454 switch (
event->key()) {
463 modifiers &= ~keyToModifier(
event->key());
465 if ((modifiers & m_currentModifiers) < m_currentModifiers) {
466 constexpr auto releaseTimeout = std::chrono::milliseconds(200);
467 const auto currentTime = std::chrono::steady_clock::now().time_since_epoch();
468 if (!m_isReleasingModifierOnly) {
469 m_isReleasingModifierOnly =
true;
470 m_modifierFirstReleaseTime = currentTime;
472 if (m_modifierOnlyAllowed && !modifiers && (currentTime - m_modifierFirstReleaseTime) < releaseTimeout) {
473 m_currentKeySequence = appendToSequence(m_currentKeySequence, prettifyModifierOnly(m_lastPressedModifiers));
476 m_currentModifiers = modifiers;
477 Q_EMIT q->currentKeySequenceChanged();
478 if (m_currentKeySequence.count() == (m_multiKeyShortcutsAllowed ? MaxKeyCount : 1)) {
481 controlModifierlessTimeout();
485void KKeySequenceRecorderPrivate::receivedRecording()
487 m_modifierlessTimer.stop();
488 m_isRecording =
false;
491 m_isReleasingModifierOnly =
false;
493 m_inhibition->disableInhibition();
496 Q_EMIT q->recordingChanged();
499void KKeySequenceRecorderPrivate::finishRecording()
502 Q_EMIT q->gotKeySequence(m_currentKeySequence);
507 , d(new KKeySequenceRecorderPrivate(this))
509 d->m_isRecording =
false;
510 d->m_modifierlessAllowed =
false;
511 d->m_multiKeyShortcutsAllowed =
true;
514 connect(&d->m_modifierlessTimer, &
QTimer::timeout, d.get(), &KKeySequenceRecorderPrivate::finishRecording);
517KKeySequenceRecorder::~KKeySequenceRecorder() noexcept
519 if (d->m_inhibition && d->m_inhibition->shortcutsAreInhibited()) {
520 d->m_inhibition->disableInhibition();
526 d->m_previousKeySequence = d->m_currentKeySequence;
528 KKeySequenceRecorderGlobal::self()->sequenceRecordingStarted();
529 connect(KKeySequenceRecorderGlobal::self(),
530 &KKeySequenceRecorderGlobal::sequenceRecordingStarted,
536 qCWarning(KGUIADDONS_LOG) <<
"Cannot record without a window";
539 d->m_isRecording =
true;
541 if (d->m_inhibition) {
542 d->m_inhibition->enableInhibition();
544 Q_EMIT recordingChanged();
545 Q_EMIT currentKeySequenceChanged();
550 setCurrentKeySequence(d->m_previousKeySequence);
551 d->receivedRecording();
557 return d->m_isRecording;
564 if (d->m_isRecording && d->m_currentKeySequence.count() < KKeySequenceRecorderPrivate::MaxKeyCount) {
565 return appendToSequence(d->m_currentKeySequence, d->m_currentModifiers);
567 return d->m_currentKeySequence;
571void KKeySequenceRecorder::setCurrentKeySequence(
const QKeySequence &sequence)
573 if (d->m_currentKeySequence == sequence) {
576 d->m_currentKeySequence = sequence;
577 Q_EMIT currentKeySequenceChanged();
585void KKeySequenceRecorder::setWindow(
QWindow *window)
587 if (
window == d->m_window) {
592 d->m_window->removeEventFilter(d.get());
596 window->installEventFilter(d.get());
597 qCDebug(KGUIADDONS_LOG) <<
"listening for events in" <<
window;
600 if (qGuiApp->platformName() == QLatin1String(
"wayland")) {
602 d->m_inhibition.reset(
new WaylandInhibition(
window));
605 d->m_inhibition.reset(
new KeyboardGrabber(
window));
615 return d->m_multiKeyShortcutsAllowed;
618void KKeySequenceRecorder::setMultiKeyShortcutsAllowed(
bool allowed)
620 if (allowed == d->m_multiKeyShortcutsAllowed) {
623 d->m_multiKeyShortcutsAllowed = allowed;
624 Q_EMIT multiKeyShortcutsAllowedChanged();
629 return d->m_modifierlessAllowed;
632void KKeySequenceRecorder::setModifierlessAllowed(
bool allowed)
634 if (allowed == d->m_modifierlessAllowed) {
637 d->m_modifierlessAllowed = allowed;
638 Q_EMIT modifierlessAllowedChanged();
643 return d->m_modifierOnlyAllowed;
646void KKeySequenceRecorder::setModifierOnlyAllowed(
bool allowed)
648 if (allowed == d->m_modifierOnlyAllowed) {
651 d->m_modifierOnlyAllowed = allowed;
652 Q_EMIT modifierOnlyAllowedChanged();
655#include "kkeysequencerecorder.moc"
656#include "moc_kkeysequencerecorder.cpp"
Record a QKeySequence by listening to key events in a window.
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.
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)
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