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"