File indexing completed on 2024-10-13 10:45:58
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