Warning, file /plasma/plasma-workspace/kcms/region_language/kcmregionandlang.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 kcmregionandlang.cpp 0003 SPDX-FileCopyrightText: 2014 Sebastian Kügler <sebas@kde.org> 0004 SPDX-FileCopyrightText: 2021 Han Young <hanyoung@protonmail.com> 0005 SPDX-FileCopyrightText: 2023 Serenity Cybersecurity, LLC <license@futurecrew.ru> 0006 Author: Gleb Popov <arrowd@FreeBSD.org> 0007 0008 SPDX-License-Identifier: GPL-2.0-or-later 0009 */ 0010 0011 #include "kcmregionandlang.h" 0012 0013 #include <unistd.h> 0014 0015 #include <QDBusConnection> 0016 #include <QDBusMessage> 0017 #include <QDBusPendingCall> 0018 0019 #include <KLocalizedString> 0020 #include <KPluginFactory> 0021 #include <KSharedConfig> 0022 0023 #include "languagelistmodel.h" 0024 #include "localegenerator.h" 0025 #include "localegeneratorbase.h" 0026 #include "localelistmodel.h" 0027 #include "optionsmodel.h" 0028 #include "regionandlangsettings.h" 0029 0030 using namespace KCM_RegionAndLang; 0031 0032 K_PLUGIN_CLASS_WITH_JSON(KCMRegionAndLang, "kcm_regionandlang.json") 0033 0034 KCMRegionAndLang::KCMRegionAndLang(QObject *parent, const KPluginMetaData &data, const QVariantList &args) 0035 : KQuickAddons::ManagedConfigModule(parent, data, args) 0036 , m_settings(new RegionAndLangSettings(this)) 0037 , m_optionsModel(new OptionsModel(this)) 0038 , m_generator(LocaleGenerator::getGenerator()) 0039 { 0040 connect(m_generator, &LocaleGeneratorBase::userHasToGenerateManually, this, &KCMRegionAndLang::userHasToGenerateManually); 0041 connect(m_generator, &LocaleGeneratorBase::success, this, &KCMRegionAndLang::generateFinished); 0042 connect(m_generator, &LocaleGeneratorBase::needsFont, this, &KCMRegionAndLang::requireInstallFont); 0043 connect(m_generator, &LocaleGeneratorBase::success, this, &KCMRegionAndLang::saveToConfigFile); 0044 connect(m_generator, &LocaleGeneratorBase::userHasToGenerateManually, this, &KCMRegionAndLang::saveToConfigFile); 0045 connect(m_generator, &LocaleGeneratorBase::needsFont, this, &KCMRegionAndLang::saveToConfigFile); 0046 0047 // if we don't support auto locale generation for current system (BSD, musl etc.), userHasToGenerateManually regarded as success 0048 if (strcmp(m_generator->metaObject()->className(), "LocaleGeneratorBase") != 0) { 0049 connect(m_generator, &LocaleGeneratorBase::success, this, &KCMRegionAndLang::takeEffectNextTime); 0050 } else { 0051 connect(m_generator, &LocaleGeneratorBase::userHasToGenerateManually, this, &KCMRegionAndLang::takeEffectNextTime); 0052 } 0053 0054 setQuickHelp(i18n("You can configure the formats used for time, dates, money and other numbers here.")); 0055 0056 qmlRegisterAnonymousType<RegionAndLangSettings>("kcmregionandlang", 1); 0057 qmlRegisterAnonymousType<OptionsModel>("kcmregionandlang", 1); 0058 qmlRegisterAnonymousType<SelectedLanguageModel>("kcmregionandlang", 1); 0059 qmlRegisterType<LocaleListModel>("kcmregionandlang", 1, 0, "LocaleListModel"); 0060 qmlRegisterType<LanguageListModel>("kcmregionandlang", 1, 0, "LanguageListModel"); 0061 qRegisterMetaType<KCM_RegionAndLang::SettingType>(); 0062 qmlRegisterUncreatableMetaObject(KCM_RegionAndLang::staticMetaObject, "kcmregionandlang", 1, 0, "SettingType", "Error: SettingType is an enum"); 0063 0064 #if GLIBC_LOCALE_GENERATED 0065 // fedora pre generate locales, fetch available locales from localectl. /usr/share/i18n/locales is empty in fedora 0066 QDir glibcLocaleDir(localeFileDirPath()); 0067 if (glibcLocaleDir.isEmpty()) { 0068 auto localectlPath = QStandardPaths::findExecutable(QStringLiteral("localectl")); 0069 if (!localectlPath.isEmpty()) { 0070 m_localectl = new QProcess(this); 0071 m_localectl->setProgram(localectlPath); 0072 m_localectl->setArguments({QStringLiteral("list-locales")}); 0073 connect(m_localectl, &QProcess::finished, this, [this](int exitCode, QProcess::ExitStatus status) { 0074 m_enabled = true; // set to true even if failed. otherwise our failed notification is also grey out 0075 if (exitCode != 0 || status != QProcess::NormalExit) { 0076 Q_EMIT encountedError(failedFindLocalesMessage()); 0077 } 0078 Q_EMIT enabledChanged(); 0079 }); 0080 m_localectl->start(); 0081 } 0082 } else { 0083 m_enabled = true; 0084 } 0085 #else 0086 m_enabled = true; 0087 #endif 0088 } 0089 0090 QString KCMRegionAndLang::failedFindLocalesMessage() 0091 { 0092 return xi18nc("@info this will be shown as an error message", 0093 "Could not find the system's available locales using the <command>localectl</command> tool. Please file a bug report about this at <link>https://bugs.kde.org</link>"); 0094 } 0095 0096 QString KCMRegionAndLang::localeFileDirPath() 0097 { 0098 return QStringLiteral("/usr/share/i18n/locales"); 0099 } 0100 0101 void KCMRegionAndLang::save() 0102 { 0103 // assemble full locales in use 0104 QStringList locales; 0105 if (!settings()->isDefaultSetting(SettingType::Lang)) { 0106 locales.append(settings()->lang()); 0107 } 0108 if (!settings()->isDefaultSetting(SettingType::Numeric)) { 0109 locales.append(settings()->numeric()); 0110 } 0111 if (!settings()->isDefaultSetting(SettingType::Time)) { 0112 locales.append(settings()->time()); 0113 } 0114 if (!settings()->isDefaultSetting(SettingType::Measurement)) { 0115 locales.append(settings()->measurement()); 0116 } 0117 if (!settings()->isDefaultSetting(SettingType::Currency)) { 0118 locales.append(settings()->monetary()); 0119 } 0120 if (!settings()->isDefaultSetting(SettingType::PaperSize)) { 0121 locales.append(settings()->paperSize()); 0122 } 0123 if (!settings()->isDefaultSetting(SettingType::Address)) { 0124 locales.append(settings()->address()); 0125 } 0126 if (!settings()->isDefaultSetting(SettingType::NameStyle)) { 0127 locales.append(settings()->nameStyle()); 0128 } 0129 if (!settings()->isDefaultSetting(SettingType::PhoneNumbers)) { 0130 locales.append(settings()->phoneNumbers()); 0131 } 0132 #ifdef GLIBC_LOCALE 0133 if (!settings()->language().isEmpty()) { 0134 QStringList languages = settings()->language().split(QLatin1Char(':')); 0135 for (const QString &lang : languages) { 0136 auto glibcLocale = toGlibcLocale(lang); 0137 if (glibcLocale.has_value()) { 0138 locales.append(glibcLocale.value()); 0139 } 0140 } 0141 } 0142 #endif 0143 0144 auto setLangCall = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.Accounts"), 0145 QStringLiteral("/org/freedesktop/Accounts/User%1").arg(getuid()), 0146 QStringLiteral("org.freedesktop.Accounts.User"), 0147 QStringLiteral("SetLanguage")); 0148 setLangCall.setArguments({settings()->lang()}); 0149 QDBusConnection::systemBus().asyncCall(setLangCall); 0150 0151 if (!locales.isEmpty()) { 0152 Q_EMIT startGenerateLocale(); 0153 m_generator->localesGenerate(locales); 0154 } else { 0155 // probably after clicking "defaults" so all the setting is default 0156 saveToConfigFile(); 0157 } 0158 Q_EMIT saveClicked(); 0159 } 0160 0161 void KCMRegionAndLang::saveToConfigFile() 0162 { 0163 KQuickAddons::ManagedConfigModule::save(); 0164 } 0165 0166 RegionAndLangSettings *KCMRegionAndLang::settings() const 0167 { 0168 return m_settings; 0169 } 0170 0171 OptionsModel *KCMRegionAndLang::optionsModel() const 0172 { 0173 return m_optionsModel; 0174 } 0175 0176 void KCMRegionAndLang::unset(SettingType setting) 0177 { 0178 const char *entry = nullptr; 0179 if (setting == SettingType::Lang) { 0180 entry = "LANG"; 0181 settings()->setLang(settings()->defaultLangValue()); 0182 } else if (setting == SettingType::Numeric) { 0183 entry = "LC_NUMERIC"; 0184 settings()->setNumeric(settings()->defaultNumericValue()); 0185 } else if (setting == SettingType::Time) { 0186 entry = "LC_TIME"; 0187 settings()->setTime(settings()->defaultTimeValue()); 0188 } else if (setting == SettingType::Measurement) { 0189 entry = "LC_MEASUREMENT"; 0190 settings()->setMeasurement(settings()->defaultMeasurementValue()); 0191 } else if (setting == SettingType::Currency) { 0192 entry = "LC_MONETARY"; 0193 settings()->setMonetary(settings()->defaultMonetaryValue()); 0194 } else if (setting == SettingType::PaperSize) { 0195 entry = "LC_PAPER"; 0196 settings()->setPaperSize(settings()->defaultPaperSizeValue()); 0197 } else if (setting == SettingType::Address) { 0198 entry = "LC_ADDRESS"; 0199 settings()->setAddress(settings()->defaultAddressValue()); 0200 } else if (setting == SettingType::NameStyle) { 0201 entry = "LC_NAME"; 0202 settings()->setNameStyle(settings()->defaultNameStyleValue()); 0203 } else if (setting == SettingType::PhoneNumbers) { 0204 entry = "LC_TELEPHONE"; 0205 settings()->setPhoneNumbers(settings()->defaultPhoneNumbersValue()); 0206 } 0207 0208 settings()->config()->group(QStringLiteral("Formats")).deleteEntry(entry); 0209 } 0210 0211 void KCMRegionAndLang::reboot() 0212 { 0213 auto method = QDBusMessage::createMethodCall(QStringLiteral("org.kde.LogoutPrompt"), 0214 QStringLiteral("/LogoutPrompt"), 0215 QStringLiteral("org.kde.LogoutPrompt"), 0216 QStringLiteral("promptReboot")); 0217 QDBusConnection::sessionBus().asyncCall(method); 0218 } 0219 0220 bool KCMRegionAndLang::isGlibc() 0221 { 0222 #ifdef OS_UBUNTU 0223 return true; 0224 #elif defined(GLIBC_LOCALE) 0225 return true; 0226 #else 0227 return false; 0228 #endif 0229 } 0230 0231 bool KCMRegionAndLang::enabled() const 0232 { 0233 return m_enabled; 0234 } 0235 0236 #ifdef GLIBC_LOCALE 0237 std::optional<QString> KCMRegionAndLang::toGlibcLocale(const QString &lang) 0238 { 0239 static std::unordered_map<QString, QString> map = constructGlibcLocaleMap(); 0240 0241 if (map.count(lang)) { 0242 return map[lang]; 0243 } 0244 return std::nullopt; 0245 } 0246 #endif 0247 0248 QString KCMRegionAndLang::toUTF8Locale(const QString &locale) 0249 { 0250 if (locale.contains(QLatin1String("UTF-8"))) { 0251 return locale; 0252 } 0253 0254 if (locale.contains(QLatin1Char('@'))) { 0255 // uz_UZ@cyrillic to uz_UZ.UTF-8@cyrillic 0256 auto localeDup = locale; 0257 localeDup.replace(QLatin1Char('@'), QLatin1String(".UTF-8@")); 0258 return localeDup; 0259 } 0260 0261 return locale + QLatin1String(".UTF-8"); 0262 } 0263 0264 #ifdef GLIBC_LOCALE 0265 std::unordered_map<QString, QString> KCMRegionAndLang::constructGlibcLocaleMap() 0266 { 0267 std::unordered_map<QString, QString> localeMap; 0268 0269 QDir glibcLocaleDir(localeFileDirPath()); 0270 auto availableLocales = glibcLocaleDir.entryList(QDir::Files); 0271 // not glibc system or corrupted system 0272 if (availableLocales.isEmpty()) { 0273 if (m_localectl) { 0274 availableLocales = QString(m_localectl->readAllStandardOutput()).split('\n'); 0275 } 0276 if (availableLocales.isEmpty()) { 0277 Q_EMIT encountedError(failedFindLocalesMessage()); 0278 return localeMap; 0279 } 0280 } 0281 0282 // map base locale code to actual glibc locale filename: "en" => ["en_US", "en_GB"] 0283 std::unordered_map<QString, std::vector<QString>> baseLocaleMap(availableLocales.size()); 0284 for (const auto &glibcLocale : availableLocales) { 0285 // we want only absolute base locale code, for sr@ijekavian and en_US, we get sr and en 0286 auto baseLocale = glibcLocale.split('_')[0].split('@')[0]; 0287 if (baseLocaleMap.count(baseLocale)) { 0288 baseLocaleMap[baseLocale].push_back(glibcLocale); 0289 } else { 0290 baseLocaleMap.insert({baseLocale, {glibcLocale}}); 0291 } 0292 } 0293 0294 auto plasmaLocales = KLocalizedString::availableDomainTranslations(QByteArrayLiteral("plasmashell")).values(); 0295 for (const auto &plasmaLocale : plasmaLocales) { 0296 auto baseLocale = plasmaLocale.split('_')[0].split('@')[0]; 0297 if (baseLocaleMap.count(baseLocale)) { 0298 const auto &prefixedLocales = baseLocaleMap[baseLocale]; 0299 0300 // if we have one to one match, use that. Eg. en_US to en_US 0301 auto fullMatch = std::find(prefixedLocales.begin(), prefixedLocales.end(), plasmaLocale); 0302 if (fullMatch != prefixedLocales.end()) { 0303 localeMap.insert({plasmaLocale, toUTF8Locale(*fullMatch)}); 0304 continue; 0305 } 0306 0307 // language name with same country code has higher priority, eg. es_ES > es_PA, de_DE > de_DE@euro 0308 auto mainLocale = plasmaLocale + "_" + plasmaLocale.toUpper(); 0309 fullMatch = std::find(prefixedLocales.begin(), prefixedLocales.end(), mainLocale); 0310 if (fullMatch != prefixedLocales.end()) { 0311 localeMap.insert({plasmaLocale, toUTF8Locale(*fullMatch)}); 0312 continue; 0313 } 0314 0315 // we try to match the locale with least characters diff (compare language code with country code, "sv" with "SE".lower() for "sv_SE"), 0316 // so ca@valencia matches with ca_ES@valencia 0317 // bad case: ca matches with ca_AD but not ca_ES 0318 int closestMatchIndex = 0; 0319 float minDiffPercentage = 1.0; 0320 std::array<int, 255> frequencyMap = {0}; 0321 for (QChar c : plasmaLocale) { 0322 // to lower so "sv_SE" has higher priority than "sv_FI" for language "sv" 0323 frequencyMap[int(c.toLower().toLatin1())]++; 0324 } 0325 0326 int i = 0; 0327 for (const auto &glibcLocale : prefixedLocales) { 0328 auto duplicated = frequencyMap; 0329 int skipBase = baseLocale.size() + 1; // we skip "sv_" part of "sv_SE", eg. compare "SE" part with "sv" 0330 for (QChar c : glibcLocale) { 0331 if (skipBase--) { 0332 continue; 0333 } 0334 duplicated[int(c.toLower().toLatin1())]--; 0335 } 0336 int diffChars = std::reduce(duplicated.begin(), duplicated.end(), 0, [](int sum, int diff) { 0337 return sum + std::abs(diff); 0338 }); 0339 float diffPercentage = float(diffChars) / glibcLocale.size(); 0340 if (diffPercentage < minDiffPercentage) { 0341 minDiffPercentage = diffPercentage; 0342 closestMatchIndex = i; 0343 } 0344 i++; 0345 } 0346 localeMap.insert({plasmaLocale, toUTF8Locale(prefixedLocales[closestMatchIndex])}); 0347 } 0348 } 0349 return localeMap; 0350 } 0351 #endif 0352 0353 #include "kcmregionandlang.moc" 0354 #include "moc_kcmregionandlang.cpp"