1 | /* |
2 | SPDX-FileCopyrightText: 2020 David Redondo <davidedmundson@kde.org> |
3 | SPDX-FileCopyrightText: 2014 David Edmundson <davidedmundson@kde.org> |
4 | SPDX-FileCopyrightText: 1998 Mark Donohoe <donohoe@kde.org> |
5 | SPDX-FileCopyrightText: 2001 Ellis Whitehead <ellis@kde.org> |
6 | SPDX-FileCopyrightText: 2007 Andreas Hartmetz <ahartmetz@gmail.com> |
7 | |
8 | SPDX-License-Identifier: LGPL-2.1-or-later |
9 | */ |
10 | |
11 | #include "keysequencehelper.h" |
12 | |
13 | #include <QDebug> |
14 | #include <QHash> |
15 | #include <QPointer> |
16 | #include <QQmlEngine> |
17 | #include <QQuickRenderControl> |
18 | #include <QQuickWindow> |
19 | |
20 | #include <KLocalizedString> |
21 | #include <KStandardShortcut> |
22 | |
23 | #ifndef Q_OS_ANDROID |
24 | #include <KMessageBox> |
25 | #endif |
26 | |
27 | #include <config-kdeclarative.h> |
28 | #if HAVE_KGLOBALACCEL |
29 | #include <KGlobalAccel> |
30 | #include <KGlobalShortcutInfo> |
31 | #endif |
32 | |
33 | class KeySequenceHelperPrivate |
34 | { |
35 | public: |
36 | KeySequenceHelperPrivate(KeySequenceHelper *qq); |
37 | |
38 | /** |
39 | * Conflicts the key sequence @a seq with a current standard |
40 | * shortcut? |
41 | */ |
42 | bool conflictWithStandardShortcuts(const QKeySequence &seq); |
43 | |
44 | /** |
45 | * Conflicts the key sequence @a seq with a current global |
46 | * shortcut? |
47 | */ |
48 | bool conflictWithGlobalShortcuts(const QKeySequence &seq); |
49 | |
50 | /** |
51 | * Get permission to steal the shortcut @seq from the standard shortcut @a std. |
52 | */ |
53 | bool stealStandardShortcut(KStandardShortcut::StandardShortcut std, const QKeySequence &seq); |
54 | |
55 | bool checkAgainstStandardShortcuts() const |
56 | { |
57 | return checkAgainstShortcutTypes & KeySequenceHelper::StandardShortcuts; |
58 | } |
59 | |
60 | bool checkAgainstGlobalShortcuts() const |
61 | { |
62 | return checkAgainstShortcutTypes & KeySequenceHelper::GlobalShortcuts; |
63 | } |
64 | |
65 | // members |
66 | KeySequenceHelper *const q; |
67 | |
68 | //! Check the key sequence against KStandardShortcut::find() |
69 | KeySequenceHelper::ShortcutTypes checkAgainstShortcutTypes; |
70 | }; |
71 | |
72 | KeySequenceHelperPrivate::KeySequenceHelperPrivate(KeySequenceHelper *qq) |
73 | : q(qq) |
74 | , checkAgainstShortcutTypes(KeySequenceHelper::StandardShortcuts | KeySequenceHelper::GlobalShortcuts) |
75 | { |
76 | } |
77 | |
78 | KeySequenceHelper::KeySequenceHelper(QObject *parent) |
79 | : KKeySequenceRecorder(nullptr, parent) |
80 | , d(new KeySequenceHelperPrivate(this)) |
81 | { |
82 | } |
83 | |
84 | KeySequenceHelper::~KeySequenceHelper() |
85 | { |
86 | delete d; |
87 | } |
88 | |
89 | bool KeySequenceHelper::isKeySequenceAvailable(const QKeySequence &keySequence) const |
90 | { |
91 | if (keySequence.isEmpty()) { |
92 | return true; |
93 | } |
94 | bool conflict = false; |
95 | if (d->checkAgainstShortcutTypes.testFlag(flag: GlobalShortcuts)) { |
96 | conflict |= d->conflictWithGlobalShortcuts(seq: keySequence); |
97 | } |
98 | if (d->checkAgainstShortcutTypes.testFlag(flag: StandardShortcuts)) { |
99 | conflict |= d->conflictWithStandardShortcuts(seq: keySequence); |
100 | } |
101 | return !conflict; |
102 | } |
103 | |
104 | KeySequenceHelper::ShortcutTypes KeySequenceHelper::checkAgainstShortcutTypes() |
105 | { |
106 | return d->checkAgainstShortcutTypes; |
107 | } |
108 | |
109 | void KeySequenceHelper::setCheckAgainstShortcutTypes(KeySequenceHelper::ShortcutTypes types) |
110 | { |
111 | if (d->checkAgainstShortcutTypes != types) { |
112 | d->checkAgainstShortcutTypes = types; |
113 | } |
114 | Q_EMIT checkAgainstShortcutTypesChanged(); |
115 | } |
116 | |
117 | bool KeySequenceHelperPrivate::conflictWithGlobalShortcuts(const QKeySequence &keySequence) |
118 | { |
119 | #ifdef Q_OS_WIN |
120 | // on windows F12 is reserved by the debugger at all times, so we can't use it for a global shortcut |
121 | if (KeySequenceHelper::GlobalShortcuts && keySequence.toString().contains(QLatin1String("F12" ))) { |
122 | QString title = i18n("Reserved Shortcut" ); |
123 | QString message = i18n( |
124 | "The F12 key is reserved on Windows, so cannot be used for a global shortcut.\n" |
125 | "Please choose another one." ); |
126 | |
127 | KMessageBox::error(nullptr, message, title); |
128 | } |
129 | return false; |
130 | #elif HAVE_KGLOBALACCEL |
131 | if (!(checkAgainstShortcutTypes & KeySequenceHelper::GlobalShortcuts)) { |
132 | return false; |
133 | } |
134 | |
135 | // Global shortcuts are on key+modifier shortcuts. They can clash with a multi key shortcut. |
136 | QList<KGlobalShortcutInfo> others; |
137 | QList<KGlobalShortcutInfo> shadow; |
138 | QList<KGlobalShortcutInfo> shadowed; |
139 | if (!KGlobalAccel::isGlobalShortcutAvailable(seq: keySequence, component: QString())) { |
140 | others << KGlobalAccel::globalShortcutsByKey(seq: keySequence); |
141 | |
142 | // look for shortcuts shadowing |
143 | shadow << KGlobalAccel::globalShortcutsByKey(seq: keySequence, type: KGlobalAccel::MatchType::Shadows); |
144 | shadowed << KGlobalAccel::globalShortcutsByKey(seq: keySequence, type: KGlobalAccel::MatchType::Shadowed); |
145 | } |
146 | |
147 | if (!shadow.isEmpty() || !shadowed.isEmpty()) { |
148 | QString title = i18n("Global Shortcut Shadowing" ); |
149 | QString message; |
150 | if (!shadowed.isEmpty()) { |
151 | message += i18n("The '%1' key combination is shadowed by following global actions:\n" ).arg(a: keySequence.toString()); |
152 | for (const KGlobalShortcutInfo &info : std::as_const(t&: shadowed)) { |
153 | message += i18n("Action '%1' in context '%2'\n" ).arg(args: info.friendlyName(), args: info.contextFriendlyName()); |
154 | } |
155 | } |
156 | if (!shadow.isEmpty()) { |
157 | message += i18n("The '%1' key combination shadows following global actions:\n" ).arg(a: keySequence.toString()); |
158 | for (const KGlobalShortcutInfo &info : std::as_const(t&: shadow)) { |
159 | message += i18n("Action '%1' in context '%2'\n" ).arg(args: info.friendlyName(), args: info.contextFriendlyName()); |
160 | } |
161 | } |
162 | |
163 | KMessageBox::error(parent: nullptr, text: message, title); |
164 | return true; |
165 | } |
166 | |
167 | if (!others.isEmpty() && !KGlobalAccel::promptStealShortcutSystemwide(parent: nullptr, shortcuts: others, seq: keySequence)) { |
168 | return true; |
169 | } |
170 | |
171 | // The user approved stealing the shortcut. We have to steal |
172 | // it immediately because KAction::setGlobalShortcut() refuses |
173 | // to set a global shortcut that is already used. There is no |
174 | // error it just silently fails. So be nice because this is |
175 | // most likely the first action that is done in the slot |
176 | // listening to keySequenceChanged(). |
177 | KGlobalAccel::stealShortcutSystemwide(seq: keySequence); |
178 | return false; |
179 | #else |
180 | return false; |
181 | #endif |
182 | } |
183 | |
184 | bool KeySequenceHelperPrivate::conflictWithStandardShortcuts(const QKeySequence &keySequence) |
185 | { |
186 | if (!checkAgainstStandardShortcuts()) { |
187 | return false; |
188 | } |
189 | |
190 | KStandardShortcut::StandardShortcut ssc = KStandardShortcut::find(keySeq: keySequence); |
191 | if (ssc != KStandardShortcut::AccelNone && !stealStandardShortcut(std: ssc, seq: keySequence)) { |
192 | return true; |
193 | } |
194 | return false; |
195 | } |
196 | |
197 | bool KeySequenceHelperPrivate::stealStandardShortcut(KStandardShortcut::StandardShortcut std, const QKeySequence &seq) |
198 | { |
199 | #ifndef Q_OS_ANDROID |
200 | QString title = i18n("Conflict with Standard Application Shortcut" ); |
201 | QString message = i18n( |
202 | "The '%1' key combination is also used for the standard action " |
203 | "\"%2\" that some applications use.\n" |
204 | "Do you really want to use it as a global shortcut as well?" , |
205 | seq.toString(QKeySequence::NativeText), |
206 | KStandardShortcut::label(std)); |
207 | |
208 | if (KMessageBox::warningContinueCancel(parent: nullptr, text: message, title, buttonContinue: KGuiItem(i18n("Reassign" ))) != KMessageBox::Continue) { |
209 | return false; |
210 | } |
211 | return true; |
212 | #else |
213 | return false; |
214 | #endif |
215 | } |
216 | |
217 | bool KeySequenceHelper::keySequenceIsEmpty(const QKeySequence &keySequence) |
218 | { |
219 | return keySequence.isEmpty(); |
220 | } |
221 | |
222 | QString KeySequenceHelper::keySequenceNativeText(const QKeySequence &keySequence) |
223 | { |
224 | return keySequence.toString(format: QKeySequence::NativeText); |
225 | } |
226 | |
227 | QWindow *KeySequenceHelper::renderWindow(QQuickWindow *quickWindow) |
228 | { |
229 | QWindow *renderWindow = QQuickRenderControl::renderWindowFor(win: quickWindow); |
230 | auto window = renderWindow ? renderWindow : quickWindow; |
231 | // If we have CppOwnership, set it explicitly to prevent the engine taking ownership of the window |
232 | // and crashing on teardown |
233 | if (QQmlEngine::objectOwnership(window) == QQmlEngine::CppOwnership) { |
234 | QQmlEngine::setObjectOwnership(window, QQmlEngine::CppOwnership); |
235 | } |
236 | return window; |
237 | } |
238 | |
239 | #include "moc_keysequencehelper.cpp" |
240 | |