1 | /* |
2 | SPDX-FileCopyrightText: 1998 Mark Donohoe <donohoe@kde.org> |
3 | SPDX-FileCopyrightText: 2001 Ellis Whitehead <ellis@kde.org> |
4 | SPDX-FileCopyrightText: 2007 Andreas Hartmetz <ahartmetz@gmail.com> |
5 | SPDX-FileCopyrightText: 2020 David Redondo <kde@david-redondo.de> |
6 | |
7 | SPDX-License-Identifier: LGPL-2.0-or-later |
8 | */ |
9 | |
10 | #include "kkeysequencerecorder.h" |
11 | |
12 | #include "keyboardgrabber_p.h" |
13 | #include "kguiaddons_debug.h" |
14 | #include "shortcutinhibition_p.h" |
15 | #include "waylandinhibition_p.h" |
16 | |
17 | #include <QGuiApplication> |
18 | #include <QKeyEvent> |
19 | #include <QPointer> |
20 | #include <QTimer> |
21 | #include <QWindow> |
22 | |
23 | #include <array> |
24 | #include <chrono> |
25 | |
26 | /// Singleton whose only purpose is to tell us about other sequence recorders getting started |
27 | class KKeySequenceRecorderGlobal : public QObject |
28 | { |
29 | Q_OBJECT |
30 | public: |
31 | static KKeySequenceRecorderGlobal *self() |
32 | { |
33 | static KKeySequenceRecorderGlobal s_self; |
34 | return &s_self; |
35 | } |
36 | |
37 | Q_SIGNALS: |
38 | void sequenceRecordingStarted(); |
39 | }; |
40 | |
41 | class KKeySequenceRecorderPrivate : public QObject |
42 | { |
43 | Q_OBJECT |
44 | public: |
45 | // Copy of QKeySequencePrivate::MaxKeyCount from private header |
46 | enum { MaxKeyCount = 4 }; |
47 | |
48 | KKeySequenceRecorderPrivate(KKeySequenceRecorder *qq); |
49 | |
50 | bool isKeyCombinationAccepted(QKeyCombination keyCombination) const; |
51 | |
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(); |
58 | |
59 | KKeySequenceRecorder *q; |
60 | QKeySequence m_currentKeySequence; |
61 | QKeySequence m_previousKeySequence; |
62 | QPointer<QWindow> m_window; |
63 | KKeySequenceRecorder::Patterns m_patterns; |
64 | bool m_isRecording; |
65 | bool m_multiKeyShortcutsAllowed; |
66 | |
67 | Qt::KeyboardModifiers m_currentModifiers; |
68 | QTimer m_modifierlessTimer; |
69 | std::unique_ptr<ShortcutInhibition> m_inhibition; |
70 | // For use in modifier only shortcuts |
71 | Qt::KeyboardModifiers m_lastPressedModifiers; |
72 | bool m_isReleasingModifierOnly = false; |
73 | std::chrono::nanoseconds m_modifierFirstReleaseTime; |
74 | }; |
75 | |
76 | constexpr Qt::KeyboardModifiers modifierMask = Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier | Qt::KeypadModifier; |
77 | |
78 | // Copied here from KKeyServer |
79 | static bool isShiftAsModifierAllowed(int keyQt) |
80 | { |
81 | // remove any modifiers |
82 | keyQt &= ~Qt::KeyboardModifierMask; |
83 | |
84 | // Shift only works as a modifier with certain keys. It's not possible |
85 | // to enter the SHIFT+5 key sequence for me because this is handled as |
86 | // '%' by qt on my keyboard. |
87 | // The working keys are all hardcoded here :-( |
88 | if (keyQt >= Qt::Key_F1 && keyQt <= Qt::Key_F35) { |
89 | return true; |
90 | } |
91 | |
92 | if (QChar::isLetter(ucs4: keyQt)) { |
93 | return true; |
94 | } |
95 | |
96 | switch (keyQt) { |
97 | case Qt::Key_Return: |
98 | case Qt::Key_Space: |
99 | case Qt::Key_Backspace: |
100 | case Qt::Key_Tab: |
101 | case Qt::Key_Backtab: |
102 | case Qt::Key_Escape: |
103 | case Qt::Key_Print: |
104 | case Qt::Key_ScrollLock: |
105 | case Qt::Key_Pause: |
106 | case Qt::Key_PageUp: |
107 | case Qt::Key_PageDown: |
108 | case Qt::Key_Insert: |
109 | case Qt::Key_Delete: |
110 | case Qt::Key_Home: |
111 | case Qt::Key_End: |
112 | case Qt::Key_Up: |
113 | case Qt::Key_Down: |
114 | case Qt::Key_Left: |
115 | case Qt::Key_Right: |
116 | case Qt::Key_Enter: |
117 | case Qt::Key_SysReq: |
118 | case Qt::Key_CapsLock: |
119 | case Qt::Key_NumLock: |
120 | case Qt::Key_Help: |
121 | case Qt::Key_Back: |
122 | case Qt::Key_Forward: |
123 | case Qt::Key_Stop: |
124 | case Qt::Key_Refresh: |
125 | case Qt::Key_Favorites: |
126 | case Qt::Key_LaunchMedia: |
127 | case Qt::Key_OpenUrl: |
128 | case Qt::Key_HomePage: |
129 | case Qt::Key_Search: |
130 | case Qt::Key_VolumeDown: |
131 | case Qt::Key_VolumeMute: |
132 | case Qt::Key_VolumeUp: |
133 | case Qt::Key_BassBoost: |
134 | case Qt::Key_BassUp: |
135 | case Qt::Key_BassDown: |
136 | case Qt::Key_TrebleUp: |
137 | case Qt::Key_TrebleDown: |
138 | case Qt::Key_MediaPlay: |
139 | case Qt::Key_MediaStop: |
140 | case Qt::Key_MediaPrevious: |
141 | case Qt::Key_MediaNext: |
142 | case Qt::Key_MediaRecord: |
143 | case Qt::Key_MediaPause: |
144 | case Qt::Key_MediaTogglePlayPause: |
145 | case Qt::Key_LaunchMail: |
146 | case Qt::Key_Calculator: |
147 | case Qt::Key_Memo: |
148 | case Qt::Key_ToDoList: |
149 | case Qt::Key_Calendar: |
150 | case Qt::Key_PowerDown: |
151 | case Qt::Key_ContrastAdjust: |
152 | case Qt::Key_Standby: |
153 | case Qt::Key_MonBrightnessUp: |
154 | case Qt::Key_MonBrightnessDown: |
155 | case Qt::Key_KeyboardLightOnOff: |
156 | case Qt::Key_KeyboardBrightnessUp: |
157 | case Qt::Key_KeyboardBrightnessDown: |
158 | case Qt::Key_PowerOff: |
159 | case Qt::Key_WakeUp: |
160 | case Qt::Key_Eject: |
161 | case Qt::Key_ScreenSaver: |
162 | case Qt::Key_WWW: |
163 | case Qt::Key_Sleep: |
164 | case Qt::Key_LightBulb: |
165 | case Qt::Key_Shop: |
166 | case Qt::Key_History: |
167 | case Qt::Key_AddFavorite: |
168 | case Qt::Key_HotLinks: |
169 | case Qt::Key_BrightnessAdjust: |
170 | case Qt::Key_Finance: |
171 | case Qt::Key_Community: |
172 | case Qt::Key_AudioRewind: |
173 | case Qt::Key_BackForward: |
174 | case Qt::Key_ApplicationLeft: |
175 | case Qt::Key_ApplicationRight: |
176 | case Qt::Key_Book: |
177 | case Qt::Key_CD: |
178 | case Qt::Key_Clear: |
179 | case Qt::Key_ClearGrab: |
180 | case Qt::Key_Close: |
181 | case Qt::Key_Copy: |
182 | case Qt::Key_Cut: |
183 | case Qt::Key_Display: |
184 | case Qt::Key_DOS: |
185 | case Qt::Key_Documents: |
186 | case Qt::Key_Excel: |
187 | case Qt::Key_Explorer: |
188 | case Qt::Key_Game: |
189 | case Qt::Key_Go: |
190 | case Qt::Key_iTouch: |
191 | case Qt::Key_LogOff: |
192 | case Qt::Key_Market: |
193 | case Qt::Key_Meeting: |
194 | case Qt::Key_MenuKB: |
195 | case Qt::Key_MenuPB: |
196 | case Qt::Key_MySites: |
197 | case Qt::Key_News: |
198 | case Qt::Key_OfficeHome: |
199 | case Qt::Key_Option: |
200 | case Qt::Key_Paste: |
201 | case Qt::Key_Phone: |
202 | case Qt::Key_Reply: |
203 | case Qt::Key_Reload: |
204 | case Qt::Key_RotateWindows: |
205 | case Qt::Key_RotationPB: |
206 | case Qt::Key_RotationKB: |
207 | case Qt::Key_Save: |
208 | case Qt::Key_Send: |
209 | case Qt::Key_Spell: |
210 | case Qt::Key_SplitScreen: |
211 | case Qt::Key_Support: |
212 | case Qt::Key_TaskPane: |
213 | case Qt::Key_Terminal: |
214 | case Qt::Key_Tools: |
215 | case Qt::Key_Travel: |
216 | case Qt::Key_Video: |
217 | case Qt::Key_Word: |
218 | case Qt::Key_Xfer: |
219 | case Qt::Key_ZoomIn: |
220 | case Qt::Key_ZoomOut: |
221 | case Qt::Key_Away: |
222 | case Qt::Key_Messenger: |
223 | case Qt::Key_WebCam: |
224 | case Qt::Key_MailForward: |
225 | case Qt::Key_Pictures: |
226 | case Qt::Key_Music: |
227 | case Qt::Key_Battery: |
228 | case Qt::Key_Bluetooth: |
229 | case Qt::Key_WLAN: |
230 | case Qt::Key_UWB: |
231 | case Qt::Key_AudioForward: |
232 | case Qt::Key_AudioRepeat: |
233 | case Qt::Key_AudioRandomPlay: |
234 | case Qt::Key_Subtitle: |
235 | case Qt::Key_AudioCycleTrack: |
236 | case Qt::Key_Time: |
237 | case Qt::Key_Select: |
238 | case Qt::Key_View: |
239 | case Qt::Key_TopMenu: |
240 | case Qt::Key_Suspend: |
241 | case Qt::Key_Hibernate: |
242 | case Qt::Key_Launch0: |
243 | case Qt::Key_Launch1: |
244 | case Qt::Key_Launch2: |
245 | case Qt::Key_Launch3: |
246 | case Qt::Key_Launch4: |
247 | case Qt::Key_Launch5: |
248 | case Qt::Key_Launch6: |
249 | case Qt::Key_Launch7: |
250 | case Qt::Key_Launch8: |
251 | case Qt::Key_Launch9: |
252 | case Qt::Key_LaunchA: |
253 | case Qt::Key_LaunchB: |
254 | case Qt::Key_LaunchC: |
255 | case Qt::Key_LaunchD: |
256 | case Qt::Key_LaunchE: |
257 | case Qt::Key_LaunchF: |
258 | case Qt::Key_Shift: |
259 | case Qt::Key_Control: |
260 | case Qt::Key_Meta: |
261 | case Qt::Key_Alt: |
262 | case Qt::Key_Super_L: |
263 | case Qt::Key_Super_R: |
264 | // Copilot key reports as Meta+Shift+TouchpadOff... |
265 | case Qt::Key_TouchpadOff: |
266 | return true; |
267 | |
268 | default: |
269 | return false; |
270 | } |
271 | } |
272 | |
273 | static bool isOkWhenModifierless(int key) |
274 | { |
275 | // this whole function is a hack, but especially the first line of code |
276 | if (QKeySequence(key).toString().length() == 1) { |
277 | return false; |
278 | } |
279 | |
280 | switch (key) { |
281 | case Qt::Key_Return: |
282 | case Qt::Key_Space: |
283 | case Qt::Key_Tab: |
284 | case Qt::Key_Backtab: // does this ever happen? |
285 | case Qt::Key_Backspace: |
286 | case Qt::Key_Delete: |
287 | return false; |
288 | default: |
289 | return true; |
290 | } |
291 | } |
292 | |
293 | bool KKeySequenceRecorderPrivate::isKeyCombinationAccepted(QKeyCombination keyCombination) const |
294 | { |
295 | const bool inputIncludesModifiers = keyCombination.keyboardModifiers(); |
296 | const bool inputIncludesKey = keyCombination.key(); |
297 | |
298 | const KKeySequenceRecorder::Pattern basePatterns[] = { |
299 | KKeySequenceRecorder::Key, |
300 | KKeySequenceRecorder::Modifier, |
301 | KKeySequenceRecorder::ModifierAndKey, |
302 | }; |
303 | |
304 | for (const auto &pattern : basePatterns) { |
305 | if (!(m_patterns & pattern)) { |
306 | continue; |
307 | } |
308 | |
309 | const bool patternIncludesModifiers = pattern & (KKeySequenceRecorder::Modifier | KKeySequenceRecorder::ModifierAndKey); |
310 | const bool patternIncludesKey = pattern & (KKeySequenceRecorder::Key | KKeySequenceRecorder::ModifierAndKey); |
311 | |
312 | if (inputIncludesModifiers != patternIncludesModifiers) { |
313 | // TODO KF7: drop isOkWhenModifierless() and require users to pass the Key pattern explicitly |
314 | if (!(patternIncludesModifiers && patternIncludesKey && isOkWhenModifierless(key: keyCombination.key()))) { |
315 | continue; |
316 | } |
317 | } |
318 | |
319 | if (inputIncludesKey == patternIncludesKey) { |
320 | return true; |
321 | } |
322 | } |
323 | |
324 | return false; |
325 | } |
326 | |
327 | static QKeySequence appendToSequence(const QKeySequence &sequence, int key) |
328 | { |
329 | if (sequence.count() >= KKeySequenceRecorderPrivate::MaxKeyCount) { |
330 | qCWarning(KGUIADDONS_LOG) << "Cannot append to a key to a sequence which is already of length" << sequence.count(); |
331 | return sequence; |
332 | } |
333 | |
334 | std::array<int, KKeySequenceRecorderPrivate::MaxKeyCount> keys{sequence[0].toCombined(), |
335 | sequence[1].toCombined(), |
336 | sequence[2].toCombined(), |
337 | sequence[3].toCombined()}; |
338 | // When the user presses Mod(s)+Alt+Print, the SysReq event is fired only |
339 | // when the Alt key is released. Before we get the Mod(s)+SysReq event, we |
340 | // first get a Mod(s)+Alt event, which we have to ignore. |
341 | // Known limitation: only works when the Alt key is released before the Mod(s) key(s). |
342 | if ((key & ~Qt::KeyboardModifierMask) == Qt::Key_SysReq) { |
343 | key = Qt::Key_Print | (key & Qt::KeyboardModifierMask) | Qt::AltModifier; |
344 | if (sequence.count() > 0 && (sequence[sequence.count() - 1].toCombined() & ~Qt::KeyboardModifierMask) == Qt::Key_Alt) { |
345 | keys[sequence.count() - 1] = key; |
346 | return QKeySequence(keys[0], keys[1], keys[2], keys[3]); |
347 | } |
348 | } |
349 | keys[sequence.count()] = key; |
350 | return QKeySequence(keys[0], keys[1], keys[2], keys[3]); |
351 | } |
352 | |
353 | KKeySequenceRecorderPrivate::KKeySequenceRecorderPrivate(KKeySequenceRecorder *qq) |
354 | : QObject(qq) |
355 | , q(qq) |
356 | { |
357 | } |
358 | |
359 | void KKeySequenceRecorderPrivate::controlModifierlessTimeout() |
360 | { |
361 | if (m_currentKeySequence != 0 && !m_currentModifiers) { |
362 | // No modifier key pressed currently. Start the timeout |
363 | m_modifierlessTimer.start(msec: 600); |
364 | } else { |
365 | // A modifier is pressed. Stop the timeout |
366 | m_modifierlessTimer.stop(); |
367 | } |
368 | } |
369 | |
370 | bool KKeySequenceRecorderPrivate::eventFilter(QObject *watched, QEvent *event) |
371 | { |
372 | if (!m_isRecording) { |
373 | return QObject::eventFilter(watched, event); |
374 | } |
375 | |
376 | if (event->type() == QEvent::ShortcutOverride || event->type() == QEvent::ContextMenu) { |
377 | event->accept(); |
378 | return true; |
379 | } |
380 | if (event->type() == QEvent::KeyRelease) { |
381 | handleKeyRelease(event: static_cast<QKeyEvent *>(event)); |
382 | return true; |
383 | } |
384 | if (event->type() == QEvent::KeyPress) { |
385 | handleKeyPress(event: static_cast<QKeyEvent *>(event)); |
386 | return true; |
387 | } |
388 | return QObject::eventFilter(watched, event); |
389 | } |
390 | |
391 | static Qt::KeyboardModifiers keyToModifier(int key) |
392 | { |
393 | switch (key) { |
394 | case Qt::Key_Meta: |
395 | case Qt::Key_Super_L: |
396 | case Qt::Key_Super_R: |
397 | // Qt doesn't properly recognize Super_L/Super_R as MetaModifier |
398 | return Qt::MetaModifier; |
399 | case Qt::Key_Shift: |
400 | return Qt::ShiftModifier; |
401 | case Qt::Key_Control: |
402 | return Qt::ControlModifier; |
403 | case Qt::Key_Alt: |
404 | return Qt::AltModifier; |
405 | default: |
406 | return Qt::NoModifier; |
407 | } |
408 | } |
409 | |
410 | void KKeySequenceRecorderPrivate::handleKeyPress(QKeyEvent *event) |
411 | { |
412 | m_isReleasingModifierOnly = false; |
413 | m_currentModifiers = event->modifiers() & modifierMask; |
414 | const int key = event->key(); |
415 | switch (key) { |
416 | case -1: |
417 | qCWarning(KGUIADDONS_LOG) << "Got unknown key" ; |
418 | // Old behavior was to stop recording here instead of continuing like this |
419 | return; |
420 | case 0: |
421 | break; |
422 | case Qt::Key_AltGr: |
423 | // or else we get unicode salad |
424 | break; |
425 | case Qt::Key_Super_L: |
426 | case Qt::Key_Super_R: |
427 | case Qt::Key_Shift: |
428 | case Qt::Key_Control: |
429 | case Qt::Key_Alt: |
430 | case Qt::Key_Meta: |
431 | m_currentModifiers |= keyToModifier(key); |
432 | m_lastPressedModifiers = m_currentModifiers; |
433 | controlModifierlessTimeout(); |
434 | Q_EMIT q->currentKeySequenceChanged(); |
435 | break; |
436 | default: |
437 | m_lastPressedModifiers = Qt::NoModifier; |
438 | |
439 | QKeyCombination keyCombination; |
440 | if ((key == Qt::Key_Backtab) && (m_currentModifiers & Qt::ShiftModifier)) { |
441 | keyCombination = QKeyCombination(m_currentModifiers, Qt::Key_Tab); |
442 | } else if (isShiftAsModifierAllowed(keyQt: key)) { |
443 | keyCombination = QKeyCombination(m_currentModifiers, Qt::Key(key)); |
444 | } else { |
445 | keyCombination = QKeyCombination(m_currentModifiers & ~Qt::ShiftModifier, Qt::Key(key)); |
446 | } |
447 | |
448 | if (!isKeyCombinationAccepted(keyCombination)) { |
449 | return; |
450 | } |
451 | |
452 | m_currentKeySequence = appendToSequence(sequence: m_currentKeySequence, key: keyCombination.toCombined()); |
453 | Q_EMIT q->currentKeySequenceChanged(); |
454 | // Now we are in a critical region (race), where recording is still |
455 | // ongoing, but key sequence has already changed (potentially) to the |
456 | // longest. But we still want currentKeySequenceChanged to trigger |
457 | // before gotKeySequence, so there's only so much we can do about it. |
458 | if ((!m_multiKeyShortcutsAllowed) || (m_currentKeySequence.count() == MaxKeyCount)) { |
459 | finishRecording(); |
460 | break; |
461 | } |
462 | controlModifierlessTimeout(); |
463 | } |
464 | event->accept(); |
465 | } |
466 | |
467 | // Turn a bunch of modifiers into mods + key |
468 | // so that the ordering is always Meta + Ctrl + Alt + Shift |
469 | static QKeyCombination prettifyModifierOnly(QKeyCombination modifierOnly) |
470 | { |
471 | const Qt::KeyboardModifiers modifier = modifierOnly.keyboardModifiers(); |
472 | if (modifier & Qt::ShiftModifier) { |
473 | return Qt::Key_Shift | (modifier & ~Qt::ShiftModifier); |
474 | } else if (modifier & Qt::AltModifier) { |
475 | return Qt::Key_Alt | (modifier & ~Qt::AltModifier); |
476 | } else if (modifier & Qt::ControlModifier) { |
477 | return Qt::Key_Control | (modifier & ~Qt::ControlModifier); |
478 | } else if (modifier & Qt::MetaModifier) { |
479 | return Qt::Key_Meta | (modifier & ~Qt::MetaModifier); |
480 | } else { |
481 | return Qt::Key(0); |
482 | } |
483 | } |
484 | |
485 | void KKeySequenceRecorderPrivate::handleKeyRelease(QKeyEvent *event) |
486 | { |
487 | Qt::KeyboardModifiers modifiers = event->modifiers() & modifierMask; |
488 | |
489 | switch (event->key()) { |
490 | case -1: |
491 | return; |
492 | case Qt::Key_Super_L: |
493 | case Qt::Key_Super_R: |
494 | case Qt::Key_Meta: |
495 | case Qt::Key_Shift: |
496 | case Qt::Key_Control: |
497 | case Qt::Key_Alt: |
498 | modifiers &= ~keyToModifier(key: event->key()); |
499 | } |
500 | if ((modifiers & m_currentModifiers) < m_currentModifiers) { |
501 | constexpr auto releaseTimeout = std::chrono::milliseconds(200); |
502 | const auto currentTime = std::chrono::steady_clock::now().time_since_epoch(); |
503 | if (!m_isReleasingModifierOnly) { |
504 | m_isReleasingModifierOnly = true; |
505 | m_modifierFirstReleaseTime = currentTime; |
506 | } |
507 | if (!modifiers && (currentTime - m_modifierFirstReleaseTime) < releaseTimeout) { |
508 | const auto keyCombination = QKeyCombination(m_lastPressedModifiers, Qt::Key(0)); |
509 | if (isKeyCombinationAccepted(keyCombination)) { |
510 | m_currentKeySequence = appendToSequence(sequence: m_currentKeySequence, key: prettifyModifierOnly(modifierOnly: keyCombination).toCombined()); |
511 | m_lastPressedModifiers = Qt::NoModifier; |
512 | } |
513 | } |
514 | m_currentModifiers = modifiers; |
515 | Q_EMIT q->currentKeySequenceChanged(); |
516 | if (m_currentKeySequence.count() == (m_multiKeyShortcutsAllowed ? MaxKeyCount : 1)) { |
517 | finishRecording(); |
518 | } |
519 | controlModifierlessTimeout(); |
520 | }; |
521 | } |
522 | |
523 | void KKeySequenceRecorderPrivate::receivedRecording() |
524 | { |
525 | m_modifierlessTimer.stop(); |
526 | m_isRecording = false; |
527 | m_currentModifiers = Qt::NoModifier; |
528 | m_lastPressedModifiers = Qt::NoModifier; |
529 | m_isReleasingModifierOnly = false; |
530 | if (m_inhibition) { |
531 | m_inhibition->disableInhibition(); |
532 | } |
533 | QObject::disconnect(sender: KKeySequenceRecorderGlobal::self(), signal: &KKeySequenceRecorderGlobal::sequenceRecordingStarted, receiver: q, slot: &KKeySequenceRecorder::cancelRecording); |
534 | Q_EMIT q->recordingChanged(); |
535 | } |
536 | |
537 | void KKeySequenceRecorderPrivate::finishRecording() |
538 | { |
539 | receivedRecording(); |
540 | Q_EMIT q->gotKeySequence(keySequence: m_currentKeySequence); |
541 | } |
542 | |
543 | KKeySequenceRecorder::KKeySequenceRecorder(QWindow *window, QObject *parent) |
544 | : QObject(parent) |
545 | , d(new KKeySequenceRecorderPrivate(this)) |
546 | { |
547 | d->m_isRecording = false; |
548 | d->m_patterns = ModifierAndKey; |
549 | d->m_multiKeyShortcutsAllowed = true; |
550 | |
551 | setWindow(window); |
552 | connect(sender: &d->m_modifierlessTimer, signal: &QTimer::timeout, context: d.get(), slot: &KKeySequenceRecorderPrivate::finishRecording); |
553 | } |
554 | |
555 | KKeySequenceRecorder::~KKeySequenceRecorder() noexcept |
556 | { |
557 | if (d->m_inhibition && d->m_inhibition->shortcutsAreInhibited()) { |
558 | d->m_inhibition->disableInhibition(); |
559 | } |
560 | } |
561 | |
562 | void KKeySequenceRecorder::startRecording() |
563 | { |
564 | d->m_previousKeySequence = d->m_currentKeySequence; |
565 | |
566 | KKeySequenceRecorderGlobal::self()->sequenceRecordingStarted(); |
567 | connect(sender: KKeySequenceRecorderGlobal::self(), |
568 | signal: &KKeySequenceRecorderGlobal::sequenceRecordingStarted, |
569 | context: this, |
570 | slot: &KKeySequenceRecorder::cancelRecording, |
571 | type: Qt::UniqueConnection); |
572 | |
573 | if (!d->m_window) { |
574 | qCWarning(KGUIADDONS_LOG) << "Cannot record without a window" ; |
575 | return; |
576 | } |
577 | d->m_isRecording = true; |
578 | d->m_currentKeySequence = QKeySequence(); |
579 | if (d->m_inhibition) { |
580 | d->m_inhibition->enableInhibition(); |
581 | } |
582 | Q_EMIT recordingChanged(); |
583 | Q_EMIT currentKeySequenceChanged(); |
584 | } |
585 | |
586 | void KKeySequenceRecorder::cancelRecording() |
587 | { |
588 | setCurrentKeySequence(d->m_previousKeySequence); |
589 | d->receivedRecording(); |
590 | Q_ASSERT(!isRecording()); |
591 | } |
592 | |
593 | bool KKeySequenceRecorder::isRecording() const |
594 | { |
595 | return d->m_isRecording; |
596 | } |
597 | |
598 | QKeySequence KKeySequenceRecorder::currentKeySequence() const |
599 | { |
600 | // We need a check for count() here because there's a race between the |
601 | // state of recording and a length of currentKeySequence. |
602 | if (d->m_isRecording && d->m_currentKeySequence.count() < KKeySequenceRecorderPrivate::MaxKeyCount) { |
603 | return appendToSequence(sequence: d->m_currentKeySequence, key: d->m_currentModifiers); |
604 | } else { |
605 | return d->m_currentKeySequence; |
606 | } |
607 | } |
608 | |
609 | void KKeySequenceRecorder::setCurrentKeySequence(const QKeySequence &sequence) |
610 | { |
611 | if (d->m_currentKeySequence == sequence) { |
612 | return; |
613 | } |
614 | d->m_currentKeySequence = sequence; |
615 | Q_EMIT currentKeySequenceChanged(); |
616 | } |
617 | |
618 | QWindow *KKeySequenceRecorder::window() const |
619 | { |
620 | return d->m_window; |
621 | } |
622 | |
623 | void KKeySequenceRecorder::setWindow(QWindow *window) |
624 | { |
625 | if (window == d->m_window) { |
626 | return; |
627 | } |
628 | |
629 | if (d->m_window) { |
630 | d->m_window->removeEventFilter(obj: d.get()); |
631 | } |
632 | |
633 | if (window) { |
634 | window->installEventFilter(filterObj: d.get()); |
635 | qCDebug(KGUIADDONS_LOG) << "listening for events in" << window; |
636 | } |
637 | |
638 | if (qGuiApp->platformName() == QLatin1String("wayland" )) { |
639 | #ifdef WITH_WAYLAND |
640 | d->m_inhibition.reset(p: new WaylandInhibition(window)); |
641 | #endif |
642 | } else { |
643 | d->m_inhibition.reset(p: new KeyboardGrabber(window)); |
644 | } |
645 | |
646 | d->m_window = window; |
647 | |
648 | Q_EMIT windowChanged(); |
649 | } |
650 | |
651 | bool KKeySequenceRecorder::multiKeyShortcutsAllowed() const |
652 | { |
653 | return d->m_multiKeyShortcutsAllowed; |
654 | } |
655 | |
656 | void KKeySequenceRecorder::setMultiKeyShortcutsAllowed(bool allowed) |
657 | { |
658 | if (allowed == d->m_multiKeyShortcutsAllowed) { |
659 | return; |
660 | } |
661 | d->m_multiKeyShortcutsAllowed = allowed; |
662 | Q_EMIT multiKeyShortcutsAllowedChanged(); |
663 | } |
664 | |
665 | #if KGUIADDONS_BUILD_DEPRECATED_SINCE(6, 12) |
666 | bool KKeySequenceRecorder::modifierlessAllowed() const |
667 | { |
668 | return d->m_patterns & Key; |
669 | } |
670 | |
671 | void KKeySequenceRecorder::setModifierlessAllowed(bool allowed) |
672 | { |
673 | if (allowed) { |
674 | setPatterns(d->m_patterns | Key); |
675 | } else { |
676 | setPatterns(d->m_patterns & ~Key); |
677 | } |
678 | } |
679 | |
680 | bool KKeySequenceRecorder::modifierOnlyAllowed() const |
681 | { |
682 | return d->m_patterns & Modifier; |
683 | } |
684 | |
685 | void KKeySequenceRecorder::setModifierOnlyAllowed(bool allowed) |
686 | { |
687 | if (allowed) { |
688 | setPatterns(d->m_patterns | Modifier); |
689 | } else { |
690 | setPatterns(d->m_patterns & ~Modifier); |
691 | } |
692 | } |
693 | #endif |
694 | |
695 | void KKeySequenceRecorder::setPatterns(Patterns patterns) |
696 | { |
697 | if (!patterns) { |
698 | return; |
699 | } |
700 | |
701 | if (patterns == d->m_patterns) { |
702 | return; |
703 | } |
704 | |
705 | #if KGUIADDONS_BUILD_DEPRECATED_SINCE(6, 12) |
706 | const bool oldModifierlessAllowed = modifierlessAllowed(); |
707 | const bool oldModifierOnlyAllowed = modifierOnlyAllowed(); |
708 | #endif |
709 | |
710 | d->m_patterns = patterns; |
711 | Q_EMIT patternsChanged(); |
712 | |
713 | #if KGUIADDONS_BUILD_DEPRECATED_SINCE(6, 12) |
714 | if (modifierlessAllowed() != oldModifierlessAllowed) { |
715 | Q_EMIT modifierlessAllowedChanged(); |
716 | } |
717 | if (modifierOnlyAllowed() != oldModifierOnlyAllowed) { |
718 | Q_EMIT modifierOnlyAllowedChanged(); |
719 | } |
720 | #endif |
721 | } |
722 | |
723 | KKeySequenceRecorder::Patterns KKeySequenceRecorder::patterns() const |
724 | { |
725 | return d->m_patterns; |
726 | } |
727 | |
728 | #include "kkeysequencerecorder.moc" |
729 | #include "moc_kkeysequencerecorder.cpp" |
730 | |