File indexing completed on 2024-05-19 11:35:49

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 #ifndef Q_OS_WIN
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     : KeySequenceRecorder(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 #else
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 #endif
0176 }
0177 
0178 bool KeySequenceHelperPrivate::conflictWithStandardShortcuts(const QKeySequence &keySequence)
0179 {
0180     if (!checkAgainstStandardShortcuts()) {
0181         return false;
0182     }
0183 
0184     KStandardShortcut::StandardShortcut ssc = KStandardShortcut::find(keySequence);
0185     if (ssc != KStandardShortcut::AccelNone && !stealStandardShortcut(ssc, keySequence)) {
0186         return true;
0187     }
0188     return false;
0189 }
0190 
0191 bool KeySequenceHelperPrivate::stealStandardShortcut(KStandardShortcut::StandardShortcut std, const QKeySequence &seq)
0192 {
0193     QString title = i18n("Conflict with Standard Application Shortcut");
0194     QString message = i18n(
0195         "The '%1' key combination is also used for the standard action "
0196         "\"%2\" that some applications use.\n"
0197         "Do you really want to use it as a global shortcut as well?",
0198         seq.toString(QKeySequence::NativeText),
0199         KStandardShortcut::label(std));
0200 
0201     if (KMessageBox::warningContinueCancel(nullptr, message, title, KGuiItem(i18n("Reassign"))) != KMessageBox::Continue) {
0202         return false;
0203     }
0204     return true;
0205 }
0206 
0207 QKeySequence KeySequenceHelper::fromString(const QString &str)
0208 {
0209     return QKeySequence::fromString(str, QKeySequence::NativeText);
0210 }
0211 
0212 bool KeySequenceHelper::keySequenceIsEmpty(const QKeySequence &keySequence)
0213 {
0214     return keySequence.isEmpty();
0215 }
0216 
0217 QString KeySequenceHelper::keySequenceNativeText(const QKeySequence &keySequence)
0218 {
0219     return keySequence.toString(QKeySequence::NativeText);
0220 }
0221 
0222 QWindow *KeySequenceHelper::renderWindow(QQuickWindow *quickWindow)
0223 {
0224     QWindow *renderWindow = QQuickRenderControl::renderWindowFor(quickWindow);
0225     auto window = renderWindow ? renderWindow : quickWindow;
0226     // If we have CppOwnership, set it explicitly to prevent the engine taking ownership of the window
0227     // and crashing on teardown
0228     if (QQmlEngine::objectOwnership(window) == QQmlEngine::CppOwnership) {
0229         QQmlEngine::setObjectOwnership(window, QQmlEngine::CppOwnership);
0230     }
0231     return window;
0232 }
0233 
0234 #include "moc_keysequencehelper.cpp"