File indexing completed on 2024-05-12 17:07:16
0001 /* 0002 SPDX-FileCopyrightText: 2011 Andriy Rysin <rysin@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "layout_memory_persister.h" 0008 #include "debug.h" 0009 0010 #include <KConfigGroup> 0011 #include <KSharedConfig> 0012 0013 #include <QDir> 0014 #include <QFile> 0015 #include <QStandardPaths> 0016 #include <qdom.h> 0017 #include <qxml.h> 0018 0019 #include "keyboard_config.h" 0020 #include "layout_memory.h" 0021 0022 static const char VERSION[] = "1.0"; 0023 static const char DOC_NAME[] = "LayoutMap"; 0024 static const char ROOT_NODE[] = "LayoutMap"; 0025 static const char VERSION_ATTRIBUTE[] = "version"; 0026 static const char SWITCH_MODE_ATTRIBUTE[] = "SwitchMode"; 0027 static const char ITEM_NODE[] = "item"; 0028 static const QString CURRENT_LAYOUT_ATTRIBUTE(QStringLiteral("currentLayout")); 0029 static const char OWNER_KEY_ATTRIBUTE[] = "ownerKey"; 0030 static const char LAYOUTS_ATTRIBUTE[] = "layouts"; 0031 0032 static const char LIST_SEPARATOR_LM[] = ","; 0033 0034 static const char REL_SESSION_FILE_PATH[] = "/keyboard/session/layout_memory.xml"; 0035 0036 static bool isDefaultLayoutConfig(const LayoutSet &layout, const QList<LayoutUnit> &defaultLayouts) 0037 { 0038 if (defaultLayouts.isEmpty() || layout.layouts != defaultLayouts || layout.currentLayout != defaultLayouts.first()) { 0039 return false; 0040 } 0041 return true; 0042 } 0043 0044 QString LayoutMemoryPersister::getLayoutMapAsString() 0045 { 0046 if (!canPersist()) 0047 return QString(); 0048 0049 QDomDocument doc(DOC_NAME); 0050 QDomElement root = doc.createElement(ROOT_NODE); 0051 root.setAttribute(VERSION_ATTRIBUTE, VERSION); 0052 root.setAttribute(SWITCH_MODE_ATTRIBUTE, layoutMemory.keyboardConfig.switchMode()); 0053 doc.appendChild(root); 0054 0055 if (layoutMemory.keyboardConfig.switchingPolicy() == KeyboardConfig::SWITCH_POLICY_GLOBAL) { 0056 if (!globalLayout.isValid()) 0057 return QString(); 0058 0059 QDomElement item = doc.createElement(ITEM_NODE); 0060 item.setAttribute(CURRENT_LAYOUT_ATTRIBUTE, globalLayout.toString()); 0061 root.appendChild(item); 0062 } else { 0063 const QStringList keys = layoutMemory.layoutMap.keys(); 0064 for (const QString &key : keys) { 0065 if (isDefaultLayoutConfig(layoutMemory.layoutMap[key], layoutMemory.keyboardConfig.getDefaultLayouts())) { 0066 continue; 0067 } 0068 0069 QDomElement item = doc.createElement(ITEM_NODE); 0070 item.setAttribute(OWNER_KEY_ATTRIBUTE, key); 0071 item.setAttribute(CURRENT_LAYOUT_ATTRIBUTE, layoutMemory.layoutMap[key].currentLayout.toString()); 0072 0073 QString layoutSetString; 0074 const QList<LayoutUnit> layouts = layoutMemory.layoutMap[key].layouts; 0075 for (const LayoutUnit &layoutUnit : layouts) { 0076 if (!layoutSetString.isEmpty()) { 0077 layoutSetString += LIST_SEPARATOR_LM; 0078 } 0079 layoutSetString += layoutUnit.toString(); 0080 } 0081 item.setAttribute(LAYOUTS_ATTRIBUTE, layoutSetString); 0082 root.appendChild(item); 0083 } 0084 } 0085 0086 return doc.toString(); 0087 } 0088 0089 bool LayoutMemoryPersister::save() 0090 { 0091 QFileInfo fileInfo(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + REL_SESSION_FILE_PATH); 0092 0093 QDir baseDir(fileInfo.absoluteDir()); 0094 if (!baseDir.exists()) { 0095 if (!QDir().mkpath(baseDir.absolutePath())) { 0096 qCWarning(KCM_KEYBOARD) << "Failed to create directory" << baseDir.absolutePath(); 0097 } 0098 } 0099 0100 QFile file(fileInfo.absoluteFilePath()); 0101 return saveToFile(file); 0102 } 0103 0104 bool LayoutMemoryPersister::restore() 0105 { 0106 QFile file(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + REL_SESSION_FILE_PATH); 0107 if (!file.exists()) { 0108 return false; 0109 } 0110 return restoreFromFile(file); 0111 } 0112 0113 bool LayoutMemoryPersister::saveToFile(const QFile &file_) 0114 { 0115 QString xml = getLayoutMapAsString(); 0116 if (xml.isEmpty()) 0117 return false; 0118 0119 QFile file(file_.fileName()); // so we don't expose the file we open/close to the caller 0120 if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { 0121 qCWarning(KCM_KEYBOARD) << "Failed to open layout memory xml file for writing" << file.fileName(); 0122 return false; 0123 } 0124 0125 QTextStream out(&file); 0126 out << xml; 0127 out.flush(); 0128 0129 if (file.error() != QFile::NoError) { 0130 qCWarning(KCM_KEYBOARD) << "Failed to store keyboard layout memory, error" << file.error(); 0131 file.close(); 0132 file.remove(); 0133 return false; 0134 } else { 0135 qCDebug(KCM_KEYBOARD) << "Keyboard layout memory stored into" << file.fileName() << "written" << file.pos(); 0136 return true; 0137 } 0138 } 0139 0140 class MapHandler : public QXmlDefaultHandler 0141 { 0142 public: 0143 MapHandler(const KeyboardConfig::SwitchingPolicy &switchingPolicy_) 0144 : verified(false) 0145 , switchingPolicy(switchingPolicy_) 0146 { 0147 } 0148 0149 bool startElement(const QString & /*namespaceURI*/, const QString & /*localName*/, const QString &qName, const QXmlAttributes &attributes) override 0150 { 0151 if (qName == ROOT_NODE) { 0152 if (attributes.value(VERSION_ATTRIBUTE) != VERSION) 0153 return false; 0154 if (attributes.value(SWITCH_MODE_ATTRIBUTE) != KeyboardConfig::getSwitchingPolicyString(switchingPolicy)) 0155 return false; 0156 0157 verified = true; 0158 } 0159 if (qName == ITEM_NODE) { 0160 if (!verified) 0161 return false; 0162 0163 if (switchingPolicy == KeyboardConfig::SWITCH_POLICY_GLOBAL) { 0164 globalLayout = LayoutUnit(attributes.value(CURRENT_LAYOUT_ATTRIBUTE)); 0165 } else { 0166 const QStringList layoutStrings = attributes.value(LAYOUTS_ATTRIBUTE).split(LIST_SEPARATOR_LM); 0167 LayoutSet layoutSet; 0168 for (const QString &layoutString : layoutStrings) { 0169 layoutSet.layouts.append(LayoutUnit(layoutString)); 0170 } 0171 layoutSet.currentLayout = LayoutUnit(attributes.value(CURRENT_LAYOUT_ATTRIBUTE)); 0172 QString ownerKey = attributes.value(OWNER_KEY_ATTRIBUTE); 0173 0174 if (ownerKey.trimmed().isEmpty() || !layoutSet.isValid()) 0175 return false; 0176 0177 layoutMap[ownerKey] = layoutSet; 0178 } 0179 } 0180 return verified; 0181 } 0182 0183 bool verified; 0184 QMap<QString, LayoutSet> layoutMap; 0185 LayoutUnit globalLayout; 0186 0187 private: 0188 const KeyboardConfig::SwitchingPolicy &switchingPolicy; 0189 }; 0190 0191 template<typename T> 0192 static bool containsAll(const QList<T> &set1, const QList<T> &set2) 0193 { 0194 for (const T &t : set2) { 0195 if (!set1.contains(t)) 0196 return false; 0197 } 0198 return true; 0199 } 0200 0201 bool LayoutMemoryPersister::restoreFromFile(const QFile &file_) 0202 { 0203 globalLayout = LayoutUnit(); 0204 0205 if (!canPersist()) 0206 return false; 0207 0208 QFile file(file_.fileName()); // so we don't expose the file we open/close to the caller 0209 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { 0210 qCWarning(KCM_KEYBOARD) << "Failed to open layout memory xml file for reading" << file.fileName() << "error:" << file.error(); 0211 return false; 0212 } 0213 0214 MapHandler mapHandler(layoutMemory.keyboardConfig.switchingPolicy()); 0215 0216 QXmlSimpleReader reader; 0217 reader.setContentHandler(&mapHandler); 0218 reader.setErrorHandler(&mapHandler); 0219 0220 QXmlInputSource xmlInputSource(&file); 0221 qCDebug(KCM_KEYBOARD) << "Restoring keyboard layout map from" << file.fileName(); 0222 0223 if (!reader.parse(xmlInputSource)) { 0224 qCWarning(KCM_KEYBOARD) << "Failed to parse the layout memory file" << file.fileName(); 0225 return false; 0226 } 0227 0228 if (layoutMemory.keyboardConfig.switchingPolicy() == KeyboardConfig::SWITCH_POLICY_GLOBAL) { 0229 if (mapHandler.globalLayout.isValid() && layoutMemory.keyboardConfig.layouts.contains(mapHandler.globalLayout)) { 0230 globalLayout = mapHandler.globalLayout; 0231 qCDebug(KCM_KEYBOARD) << "Restored global layout" << globalLayout.toString(); 0232 } 0233 } else { 0234 layoutMemory.layoutMap.clear(); 0235 for (const QString &key : mapHandler.layoutMap.keys()) { 0236 if (containsAll(layoutMemory.keyboardConfig.layouts, mapHandler.layoutMap[key].layouts)) { 0237 layoutMemory.layoutMap.insert(key, mapHandler.layoutMap[key]); 0238 } 0239 } 0240 qCDebug(KCM_KEYBOARD) << "Restored layouts for" << layoutMemory.layoutMap.size() << "containers"; 0241 } 0242 return true; 0243 } 0244 0245 bool LayoutMemoryPersister::canPersist() 0246 { 0247 // we can't persist per window - as we're using window id which is not preserved between sessions 0248 bool windowMode = layoutMemory.keyboardConfig.switchingPolicy() == KeyboardConfig::SWITCH_POLICY_WINDOW; 0249 if (windowMode) { 0250 qCDebug(KCM_KEYBOARD) << "Not saving session for window mode"; 0251 } 0252 return !windowMode; 0253 }