File indexing completed on 2024-03-24 17:02:12

0001 /**
0002  * SPDX-FileCopyrightText: 1999-2001 Lubos Lunak <l.lunak@kde.org>
0003  * SPDX-FileCopyrightText: 2009 Michael Jansen <kde@michael-jansen.biz>
0004  *
0005  * SPDX-License-Identifier: LGPL-2.0-or-later
0006  **/
0007 
0008 #include "settings.h"
0009 
0010 #include "action_data/action_data.h"
0011 #include "settings_reader_v2.h"
0012 #include "settings_writer.h"
0013 #include "windows_helper/window_selection_list.h"
0014 
0015 #include <KConfig>
0016 #include <KGlobal>
0017 #include <KMessageBox>
0018 #include <KStandardDirs>
0019 #include <QDebug>
0020 
0021 #include <QApplication>
0022 #include <QDir>
0023 #include <QStandardPaths>
0024 
0025 namespace KHotKeys
0026 {
0027 // Settings
0028 bool Settings::isOutdated = false;
0029 
0030 Settings::Settings()
0031     : m_actions(nullptr)
0032     , gestures_exclude(nullptr)
0033 {
0034     reinitialize();
0035 }
0036 
0037 Settings::~Settings()
0038 {
0039     delete m_actions;
0040     m_actions = 0;
0041 }
0042 
0043 ActionDataGroup *Settings::actions()
0044 {
0045     return m_actions;
0046 }
0047 
0048 const ActionDataGroup *Settings::actions() const
0049 {
0050     return m_actions;
0051 }
0052 
0053 bool Settings::areGesturesDisabled() const
0054 {
0055     return gestures_disabled;
0056 }
0057 
0058 void Settings::disableDaemon()
0059 {
0060     daemon_disabled = true;
0061 }
0062 
0063 void Settings::disableGestures()
0064 {
0065     gestures_disabled = true;
0066 }
0067 
0068 void Settings::enableDaemon()
0069 {
0070     daemon_disabled = false;
0071 }
0072 
0073 void Settings::enableGestures()
0074 {
0075     gestures_disabled = false;
0076 }
0077 
0078 void Settings::exportTo(ActionDataBase *what, KConfigBase &config, const QString &id, KHotKeys::ActionState state, bool allowMerging)
0079 {
0080     SettingsWriter writer(this, state, id, allowMerging);
0081     writer.exportTo(what, config);
0082 }
0083 
0084 int Settings::gestureMouseButton() const
0085 {
0086     return gesture_mouse_button;
0087 }
0088 
0089 Windowdef_list *Settings::gesturesExclude()
0090 {
0091     return gestures_exclude;
0092 }
0093 
0094 const Windowdef_list *Settings::gesturesExclude() const
0095 {
0096     return gestures_exclude;
0097 }
0098 
0099 int Settings::gestureTimeOut() const
0100 {
0101     return gesture_timeout;
0102 }
0103 
0104 bool Settings::isDaemonDisabled() const
0105 {
0106     return daemon_disabled;
0107 }
0108 
0109 bool Settings::loadDefaults()
0110 {
0111     reinitialize();
0112 
0113     // Load the default set.
0114     QString installPath = KGlobal::dirs()->installPath("data");
0115 
0116     KConfig file(installPath + "khotkeys/defaults.khotkeys");
0117     if (read_settings(m_actions, file, true, Enabled)) {
0118         qDebug() << "Loaded defaults from" << file.name();
0119         already_imported.append("defaults");
0120         return true;
0121     } else {
0122         qDebug() << "Failed to load defaults from" << file.name();
0123         return false;
0124     }
0125 }
0126 
0127 void Settings::reinitialize()
0128 {
0129     // Rereading settings. First delete what we have
0130     setActions(nullptr);
0131 
0132     gestures_disabled = true;
0133     gesture_mouse_button = 2;
0134     gesture_timeout = 300;
0135     gestures_exclude = nullptr,
0136 
0137     daemon_disabled = false;
0138 
0139     // Currently unused
0140     voice_shortcut = QKeySequence();
0141 
0142     already_imported = QStringList();
0143 }
0144 
0145 void Settings::setActions(ActionDataGroup *actions)
0146 {
0147     delete m_actions;
0148 
0149     m_actions = actions ? actions : new ActionDataGroup(nullptr, "should never see", "should never see", nullptr, ActionDataGroup::SYSTEM_ROOT);
0150     m_actions->enable();
0151 }
0152 
0153 void Settings::setGesturesExclude(Windowdef_list *gestures)
0154 {
0155     delete gestures_exclude;
0156     gestures_exclude = gestures;
0157 }
0158 
0159 void Settings::setGestureMouseButton(int mouse_button)
0160 {
0161     gesture_mouse_button = mouse_button;
0162 }
0163 
0164 void Settings::setGestureTimeOut(int timeout)
0165 {
0166     gesture_timeout = timeout;
0167 }
0168 
0169 void Settings::setVoiceShortcut(const QKeySequence &shortcut)
0170 {
0171     voice_shortcut = shortcut;
0172 }
0173 
0174 ActionDataGroup *Settings::takeActions()
0175 {
0176     ActionDataGroup *res = m_actions;
0177     m_actions = 0;
0178     return res;
0179 }
0180 
0181 QKeySequence Settings::voiceShortcut() const
0182 {
0183     return voice_shortcut;
0184 }
0185 
0186 bool Settings::import(KConfig &config, ImportType ask, ActionState state)
0187 {
0188     return importFrom(m_actions, config, ask, state);
0189 }
0190 
0191 bool Settings::isConfigFileValid(KConfigBase const &config, ImportType ask)
0192 {
0193     bool valid = false;
0194 
0195     // The file is only valid if it has a main group
0196     KConfigGroup mainGroup(&config, "Main");
0197     if (mainGroup.isValid()) {
0198         // Now check the version
0199         int version = mainGroup.readEntry("Version", -1234576);
0200         switch (version) {
0201         case 2:
0202             valid = true;
0203             break;
0204 
0205         case 1: // Version 1 files no longer supported
0206             qDebug() << "Version 1 file encountered.";
0207             break;
0208 
0209         case -1234576: // No Version entry -> invalid file
0210             qDebug() << "No version specified in file:";
0211             valid = false;
0212             break;
0213 
0214         default:
0215             qDebug() << "Invalid Version found:" << version;
0216             valid = false;
0217             break;
0218         }
0219     }
0220 
0221     // if it's valid we are finished.
0222     if (valid)
0223         return valid;
0224 
0225     // See if we should inform the user.
0226     switch (ask) {
0227     case ImportAsk: {
0228         KMessageBox::information(QApplication::activeWindow(), "The specified file is empty or not a configuration file", "Import actions");
0229     } break;
0230 
0231     case ImportSilent:
0232         break;
0233 
0234     default:
0235         Q_ASSERT(false);
0236     }
0237 
0238     return valid;
0239 }
0240 
0241 bool Settings::importFrom(ActionDataGroup *element, KConfigBase const &config, ImportType ask, ActionState state)
0242 {
0243     // Make sure the given file is valid
0244     if (!isConfigFileValid(config, ask))
0245         return false;
0246 
0247     KConfigGroup mainGroup(&config, "Main");
0248     // A file can have a import id.
0249     QString import_id = mainGroup.readEntry("ImportId");
0250     if (!import_id.isEmpty()) {
0251         // File has a id. Check for a previous import.
0252         if (already_imported.contains(import_id)) {
0253             switch (ask) {
0254             case ImportAsk:
0255                 // Ask the user?
0256                 if (ask == ImportSilent
0257                     || (ask == ImportAsk
0258                         && KMessageBox::warningContinueCancel(nullptr,
0259                                                               i18n("This \"actions\" file has already been imported before. "
0260                                                                    "Are you sure you want to import it again?"))
0261                             != KMessageBox::Continue)) {
0262                     return true; // import "successful"
0263                 }
0264                 break;
0265 
0266             case ImportSilent:
0267                 return true;
0268 
0269             default:
0270                 // Unknown ImportType. Most likely None.
0271                 Q_ASSERT(false);
0272                 return true;
0273             }
0274         } else {
0275             already_imported.append(import_id);
0276         }
0277     } else {
0278         switch (ask) {
0279         case ImportAsk:
0280             if (KMessageBox::warningContinueCancel(nullptr,
0281                                                    i18n("This \"actions\" file has no ImportId field and therefore it cannot be determined "
0282                                                         "whether or not it has been imported already. Are you sure you want to import it?"))
0283                 == KMessageBox::Cancel) {
0284                 return true;
0285             }
0286             break;
0287 
0288         case ImportSilent:
0289             return true;
0290 
0291         default:
0292             // Unknown ImportType. Most likely None.
0293             Q_ASSERT(false);
0294             return true;
0295         }
0296     }
0297 
0298     // Include Disabled, Disable the imported actions
0299     return read_settings(element, config, true, state);
0300 }
0301 
0302 void Settings::validate()
0303 {
0304     // Create the KMenuEdit group if it does not yet exist
0305     get_system_group(ActionDataGroup::SYSTEM_MENUENTRIES);
0306 }
0307 
0308 ActionDataGroup *Settings::get_system_group(ActionDataGroup::system_group_t group_id)
0309 {
0310     Q_ASSERT(m_actions);
0311 
0312     // Search for the menuentries system group.
0313     ActionDataGroup *system_group = nullptr;
0314 
0315     Q_FOREACH (KHotKeys::ActionDataBase *element, m_actions->children()) {
0316         ActionDataGroup *group = dynamic_cast<ActionDataGroup *>(element);
0317 
0318         if (group && (group->system_group() == group_id)) {
0319             system_group = group;
0320             break;
0321         }
0322     }
0323 
0324     // Check if we found the group
0325     if (system_group == nullptr) {
0326         switch (group_id) {
0327         case ActionDataGroup::SYSTEM_MENUENTRIES:
0328             system_group = new ActionDataGroup(m_actions, "KMenuEdit", "KMenuEdit Global Shortcuts", nullptr, ActionDataGroup::SYSTEM_MENUENTRIES);
0329             system_group->enable();
0330             break;
0331 
0332         default:
0333             Q_ASSERT(false);
0334             return nullptr;
0335         }
0336     }
0337 
0338     Q_ASSERT(system_group);
0339     return system_group;
0340 }
0341 
0342 bool Settings::reread_settings(bool include_disabled)
0343 {
0344     KConfig config(KHOTKEYS_CONFIG_FILE);
0345 
0346     // If we read the main settings and there is no main. Initialize the file
0347     // and return
0348     KConfigGroup mainGroup(&config, "Main"); // main group
0349     if (!mainGroup.exists()) {
0350         loadDefaults();
0351         validate();
0352         return false;
0353     }
0354 
0355     // First delete what we have
0356     reinitialize();
0357 
0358     // ### Read the global configurations. Reinitialize sets the default
0359     daemon_disabled = mainGroup.readEntry("Disabled", daemon_disabled);
0360 
0361     // ### List of already imported configuration files
0362     already_imported = mainGroup.readEntry("AlreadyImported", QStringList());
0363 
0364     // ### Gestures
0365     KConfigGroup gesturesConfig(&config, "Gestures");
0366     // ### Read the gesture configurations. Reinitialize sets the default.
0367     // Keep them
0368     gestures_disabled = gesturesConfig.readEntry("Disabled", gestures_disabled);
0369     gesture_mouse_button = gesturesConfig.readEntry("MouseButton", gesture_mouse_button);
0370     gesture_mouse_button = qBound(2, gesture_mouse_button, 9);
0371     gesture_timeout = gesturesConfig.readEntry("Timeout", gesture_timeout);
0372 
0373     // Somehow gesture_timeout found it's way into my config file. Fix it for
0374     // everyone else too.
0375     if (gesture_timeout < 100)
0376         gesture_timeout = 300;
0377 
0378     KConfigGroup gesturesExcludeConfig(&config, "GesturesExclude");
0379     delete gestures_exclude;
0380     gestures_exclude = new Windowdef_list(gesturesExcludeConfig);
0381 
0382     // ### Voice
0383     KConfigGroup voiceConfig(&config, "Voice");
0384     voice_shortcut = QKeySequence(voiceConfig.readEntry("Shortcut", ""));
0385 
0386     bool rc = read_settings(m_actions, config, include_disabled, Retain);
0387     // Ensure the system groups exist
0388     validate();
0389     return rc;
0390 }
0391 
0392 bool Settings::read_settings(ActionDataGroup *root, KConfigBase const &config, bool include_disabled, ActionState stateStrategy)
0393 {
0394     // Make sure the given file is valid
0395     if (!isConfigFileValid(config, ImportSilent))
0396         return false;
0397 
0398     KConfigGroup mainGroup(&config, "Main"); // main group
0399     int version = mainGroup.readEntry("Version", -1234576);
0400     QString import_id = mainGroup.readEntry("ImportId");
0401     switch (version) {
0402     case 2: {
0403         qDebug() << "Version 2 File!";
0404         SettingsReaderV2 reader(this, include_disabled, stateStrategy, import_id);
0405         reader.read(config, root);
0406     } break;
0407 
0408     default:
0409         // All other values are impossible because of the
0410         // isConfigFileValid() call above.
0411         Q_ASSERT(false);
0412         return false;
0413     }
0414 
0415     Settings::isOutdated = false;
0416     return true;
0417 }
0418 
0419 bool Settings::update()
0420 {
0421     const QStringList khotkeysDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "khotkeys", QStandardPaths::LocateDirectory);
0422     QStringList updates;
0423     for (const auto &dir : khotkeysDirs) {
0424         const QStringList fileNames = QDir(dir).entryList(QStringList{QStringLiteral("*.khotkeys")});
0425         std::transform(fileNames.begin(), fileNames.end(), std::back_inserter(updates), [&dir](const QString &file) {
0426             return QStringLiteral("%1/%2").arg(dir, file);
0427         });
0428     }
0429 
0430     bool imported(false);
0431 
0432     Q_FOREACH (const QString &path, updates) {
0433         // Import checks if the file was already imported.
0434         KConfig file(path);
0435         if (import(file, ImportSilent, Retain)) {
0436             qDebug() << "Imported file" << path;
0437             imported = true;
0438         }
0439     }
0440 
0441     if (imported) {
0442         write();
0443     }
0444     return false;
0445 }
0446 
0447 void Settings::write()
0448 {
0449     if (isOutdated)
0450         return; // the kcm wrote after we read it and we're trying to write because
0451                 // we got an update from kglobalaccel - ie. it's pointless and our
0452                 // info is dated. The kcm should next ask us to read settings
0453     KConfig cfg(KHOTKEYS_CONFIG_FILE);
0454     SettingsWriter writer(this, Retain);
0455     writer.writeTo(cfg);
0456 }
0457 
0458 } // namespace KHotKeys