File indexing completed on 2024-04-28 03:53:58
0001 /* 0002 SPDX-FileCopyrightText: 2020 David Redondo <davidedmundson@kde.org> 0003 SPDX-FileCopyrightText: 2014 David Edmundson <davidedmundson@kde.org> 0004 SPDX-FileCopyrightText: 1998 Mark Donohoe <donohoe@kde.org> 0005 SPDX-FileCopyrightText: 2001 Ellis Whitehead <ellis@kde.org> 0006 SPDX-FileCopyrightText: 2007 Andreas Hartmetz <ahartmetz@gmail.com> 0007 0008 SPDX-License-Identifier: LGPL-2.1-or-later 0009 */ 0010 0011 #include "keysequencehelper.h" 0012 0013 #include <QDebug> 0014 #include <QHash> 0015 #include <QPointer> 0016 #include <QQmlEngine> 0017 #include <QQuickRenderControl> 0018 #include <QQuickWindow> 0019 0020 #include <KLocalizedString> 0021 #include <KMessageBox> 0022 #include <KStandardShortcut> 0023 0024 #if !defined(Q_OS_WIN) && !defined(Q_OS_DARWIN) 0025 #include <KGlobalAccel> 0026 #include <KGlobalShortcutInfo> 0027 #endif 0028 0029 class KeySequenceHelperPrivate 0030 { 0031 public: 0032 KeySequenceHelperPrivate(KeySequenceHelper *qq); 0033 0034 /** 0035 * Conflicts the key sequence @a seq with a current standard 0036 * shortcut? 0037 */ 0038 bool conflictWithStandardShortcuts(const QKeySequence &seq); 0039 0040 /** 0041 * Conflicts the key sequence @a seq with a current global 0042 * shortcut? 0043 */ 0044 bool conflictWithGlobalShortcuts(const QKeySequence &seq); 0045 0046 /** 0047 * Get permission to steal the shortcut @seq from the standard shortcut @a std. 0048 */ 0049 bool stealStandardShortcut(KStandardShortcut::StandardShortcut std, const QKeySequence &seq); 0050 0051 bool checkAgainstStandardShortcuts() const 0052 { 0053 return checkAgainstShortcutTypes & KeySequenceHelper::StandardShortcuts; 0054 } 0055 0056 bool checkAgainstGlobalShortcuts() const 0057 { 0058 return checkAgainstShortcutTypes & KeySequenceHelper::GlobalShortcuts; 0059 } 0060 0061 // members 0062 KeySequenceHelper *const q; 0063 0064 //! Check the key sequence against KStandardShortcut::find() 0065 KeySequenceHelper::ShortcutTypes checkAgainstShortcutTypes; 0066 }; 0067 0068 KeySequenceHelperPrivate::KeySequenceHelperPrivate(KeySequenceHelper *qq) 0069 : q(qq) 0070 , checkAgainstShortcutTypes(KeySequenceHelper::StandardShortcuts | KeySequenceHelper::GlobalShortcuts) 0071 { 0072 } 0073 0074 KeySequenceHelper::KeySequenceHelper(QObject *parent) 0075 : KKeySequenceRecorder(nullptr, parent) 0076 , d(new KeySequenceHelperPrivate(this)) 0077 { 0078 } 0079 0080 KeySequenceHelper::~KeySequenceHelper() 0081 { 0082 delete d; 0083 } 0084 0085 bool KeySequenceHelper::isKeySequenceAvailable(const QKeySequence &keySequence) const 0086 { 0087 if (keySequence.isEmpty()) { 0088 return true; 0089 } 0090 bool conflict = false; 0091 if (d->checkAgainstShortcutTypes.testFlag(GlobalShortcuts)) { 0092 conflict |= d->conflictWithGlobalShortcuts(keySequence); 0093 } 0094 if (d->checkAgainstShortcutTypes.testFlag(StandardShortcuts)) { 0095 conflict |= d->conflictWithStandardShortcuts(keySequence); 0096 } 0097 return !conflict; 0098 } 0099 0100 KeySequenceHelper::ShortcutTypes KeySequenceHelper::checkAgainstShortcutTypes() 0101 { 0102 return d->checkAgainstShortcutTypes; 0103 } 0104 0105 void KeySequenceHelper::setCheckAgainstShortcutTypes(KeySequenceHelper::ShortcutTypes types) 0106 { 0107 if (d->checkAgainstShortcutTypes != types) { 0108 d->checkAgainstShortcutTypes = types; 0109 } 0110 Q_EMIT checkAgainstShortcutTypesChanged(); 0111 } 0112 0113 bool KeySequenceHelperPrivate::conflictWithGlobalShortcuts(const QKeySequence &keySequence) 0114 { 0115 #ifdef Q_OS_WIN 0116 // on windows F12 is reserved by the debugger at all times, so we can't use it for a global shortcut 0117 if (KeySequenceHelper::GlobalShortcuts && keySequence.toString().contains(QLatin1String("F12"))) { 0118 QString title = i18n("Reserved Shortcut"); 0119 QString message = i18n( 0120 "The F12 key is reserved on Windows, so cannot be used for a global shortcut.\n" 0121 "Please choose another one."); 0122 0123 KMessageBox::error(nullptr, message, title); 0124 } 0125 return false; 0126 #elif !defined(Q_OS_DARWIN) 0127 if (!(checkAgainstShortcutTypes & KeySequenceHelper::GlobalShortcuts)) { 0128 return false; 0129 } 0130 0131 // Global shortcuts are on key+modifier shortcuts. They can clash with a multi key shortcut. 0132 QList<KGlobalShortcutInfo> others; 0133 QList<KGlobalShortcutInfo> shadow; 0134 QList<KGlobalShortcutInfo> shadowed; 0135 if (!KGlobalAccel::isGlobalShortcutAvailable(keySequence, QString())) { 0136 others << KGlobalAccel::globalShortcutsByKey(keySequence); 0137 0138 // look for shortcuts shadowing 0139 shadow << KGlobalAccel::globalShortcutsByKey(keySequence, KGlobalAccel::MatchType::Shadows); 0140 shadowed << KGlobalAccel::globalShortcutsByKey(keySequence, KGlobalAccel::MatchType::Shadowed); 0141 } 0142 0143 if (!shadow.isEmpty() || !shadowed.isEmpty()) { 0144 QString title = i18n("Global Shortcut Shadowing"); 0145 QString message; 0146 if (!shadowed.isEmpty()) { 0147 message += i18n("The '%1' key combination is shadowed by following global actions:\n").arg(keySequence.toString()); 0148 for (const KGlobalShortcutInfo &info : std::as_const(shadowed)) { 0149 message += i18n("Action '%1' in context '%2'\n").arg(info.friendlyName(), info.contextFriendlyName()); 0150 } 0151 } 0152 if (!shadow.isEmpty()) { 0153 message += i18n("The '%1' key combination shadows following global actions:\n").arg(keySequence.toString()); 0154 for (const KGlobalShortcutInfo &info : std::as_const(shadow)) { 0155 message += i18n("Action '%1' in context '%2'\n").arg(info.friendlyName(), info.contextFriendlyName()); 0156 } 0157 } 0158 0159 KMessageBox::error(nullptr, message, title); 0160 return true; 0161 } 0162 0163 if (!others.isEmpty() && !KGlobalAccel::promptStealShortcutSystemwide(nullptr, others, keySequence)) { 0164 return true; 0165 } 0166 0167 // The user approved stealing the shortcut. We have to steal 0168 // it immediately because KAction::setGlobalShortcut() refuses 0169 // to set a global shortcut that is already used. There is no 0170 // error it just silently fails. So be nice because this is 0171 // most likely the first action that is done in the slot 0172 // listening to keySequenceChanged(). 0173 KGlobalAccel::stealShortcutSystemwide(keySequence); 0174 return false; 0175 #else 0176 return false; 0177 #endif 0178 } 0179 0180 bool KeySequenceHelperPrivate::conflictWithStandardShortcuts(const QKeySequence &keySequence) 0181 { 0182 if (!checkAgainstStandardShortcuts()) { 0183 return false; 0184 } 0185 0186 KStandardShortcut::StandardShortcut ssc = KStandardShortcut::find(keySequence); 0187 if (ssc != KStandardShortcut::AccelNone && !stealStandardShortcut(ssc, keySequence)) { 0188 return true; 0189 } 0190 return false; 0191 } 0192 0193 bool KeySequenceHelperPrivate::stealStandardShortcut(KStandardShortcut::StandardShortcut std, const QKeySequence &seq) 0194 { 0195 QString title = i18n("Conflict with Standard Application Shortcut"); 0196 QString message = i18n( 0197 "The '%1' key combination is also used for the standard action " 0198 "\"%2\" that some applications use.\n" 0199 "Do you really want to use it as a global shortcut as well?", 0200 seq.toString(QKeySequence::NativeText), 0201 KStandardShortcut::label(std)); 0202 0203 if (KMessageBox::warningContinueCancel(nullptr, message, title, KGuiItem(i18n("Reassign"))) != KMessageBox::Continue) { 0204 return false; 0205 } 0206 return true; 0207 } 0208 0209 QKeySequence KeySequenceHelper::fromString(const QString &str) 0210 { 0211 return QKeySequence::fromString(str, QKeySequence::NativeText); 0212 } 0213 0214 bool KeySequenceHelper::keySequenceIsEmpty(const QKeySequence &keySequence) 0215 { 0216 return keySequence.isEmpty(); 0217 } 0218 0219 QString KeySequenceHelper::keySequenceNativeText(const QKeySequence &keySequence) 0220 { 0221 return keySequence.toString(QKeySequence::NativeText); 0222 } 0223 0224 QWindow *KeySequenceHelper::renderWindow(QQuickWindow *quickWindow) 0225 { 0226 QWindow *renderWindow = QQuickRenderControl::renderWindowFor(quickWindow); 0227 auto window = renderWindow ? renderWindow : quickWindow; 0228 // If we have CppOwnership, set it explicitly to prevent the engine taking ownership of the window 0229 // and crashing on teardown 0230 if (QQmlEngine::objectOwnership(window) == QQmlEngine::CppOwnership) { 0231 QQmlEngine::setObjectOwnership(window, QQmlEngine::CppOwnership); 0232 } 0233 return window; 0234 } 0235 0236 #include "moc_keysequencehelper.cpp"