File indexing completed on 2024-05-19 05:38:05
0001 /* 0002 SPDX-FileCopyrightText: 2014 Marco Martin <mart@kde.org> 0003 SPDX-FileCopyrightText: 2014 Vishesh Handa <me@vhanda.in> 0004 SPDX-FileCopyrightText: 2016 David Rosca <nowrep@gmail.com> 0005 SPDX-FileCopyrightText: 2018 Kai Uwe Broulik <kde@privat.broulik.de> 0006 SPDX-FileCopyrightText: 2019 Kevin Ottens <kevin.ottens@enioka.com> 0007 0008 SPDX-License-Identifier: LGPL-2.0-only 0009 */ 0010 0011 #include "kcm.h" 0012 0013 #include <KLocalizedString> 0014 #include <KPluginFactory> 0015 0016 #include <KIO/FileCopyJob> 0017 #include <KIO/JobUiDelegate> 0018 0019 #include <KSvg/FrameSvg> 0020 #include <KSvg/ImageSet> 0021 #include <KSvg/Svg> 0022 0023 #include <QDBusConnection> 0024 #include <QDBusMessage> 0025 #include <QDebug> 0026 #include <QProcess> 0027 #include <QQuickItem> 0028 #include <QQuickWindow> 0029 #include <QStandardItemModel> 0030 #include <QStandardPaths> 0031 #include <QTemporaryFile> 0032 0033 #include "desktopthemedata.h" 0034 #include "filterproxymodel.h" 0035 0036 Q_LOGGING_CATEGORY(KCM_DESKTOP_THEME, "kcm_desktoptheme") 0037 0038 K_PLUGIN_FACTORY_WITH_JSON(KCMDesktopThemeFactory, "kcm_desktoptheme.json", registerPlugin<KCMDesktopTheme>(); registerPlugin<DesktopThemeData>();) 0039 0040 KCMDesktopTheme::KCMDesktopTheme(QObject *parent, const KPluginMetaData &data) 0041 : KQuickManagedConfigModule(parent, data) 0042 , m_data(new DesktopThemeData(this)) 0043 , m_model(new ThemesModel(this)) 0044 , m_filteredModel(new FilterProxyModel(this)) 0045 , m_haveThemeExplorerInstalled(false) 0046 { 0047 qmlRegisterAnonymousType<DesktopThemeSettings>("org.kde.private.kcms.desktoptheme", 1); 0048 qmlRegisterUncreatableType<ThemesModel>("org.kde.private.kcms.desktoptheme", 1, 0, "ThemesModel", QStringLiteral("Cannot create ThemesModel")); 0049 qmlRegisterUncreatableType<FilterProxyModel>("org.kde.private.kcms.desktoptheme", 0050 1, 0051 0, 0052 "FilterProxyModel", 0053 QStringLiteral("Cannot create FilterProxyModel")); 0054 0055 setButtons(Apply | Default | Help); 0056 0057 m_haveThemeExplorerInstalled = !QStandardPaths::findExecutable(QStringLiteral("plasmathemeexplorer")).isEmpty(); 0058 0059 connect(m_model, &ThemesModel::pendingDeletionsChanged, this, &KCMDesktopTheme::settingsChanged); 0060 0061 connect(m_model, &ThemesModel::selectedThemeChanged, this, [this](const QString &pluginName) { 0062 desktopThemeSettings()->setName(pluginName); 0063 }); 0064 0065 connect(desktopThemeSettings(), &DesktopThemeSettings::nameChanged, this, [this] { 0066 m_model->setSelectedTheme(desktopThemeSettings()->name()); 0067 }); 0068 0069 connect(m_model, &ThemesModel::selectedThemeChanged, m_filteredModel, &FilterProxyModel::setSelectedTheme); 0070 0071 m_filteredModel->setSourceModel(m_model); 0072 } 0073 0074 KCMDesktopTheme::~KCMDesktopTheme() 0075 { 0076 } 0077 0078 DesktopThemeSettings *KCMDesktopTheme::desktopThemeSettings() const 0079 { 0080 return m_data->settings(); 0081 } 0082 0083 ThemesModel *KCMDesktopTheme::desktopThemeModel() const 0084 { 0085 return m_model; 0086 } 0087 0088 FilterProxyModel *KCMDesktopTheme::filteredModel() const 0089 { 0090 return m_filteredModel; 0091 } 0092 0093 bool KCMDesktopTheme::downloadingFile() const 0094 { 0095 return m_tempCopyJob; 0096 } 0097 0098 void KCMDesktopTheme::installThemeFromFile(const QUrl &url) 0099 { 0100 if (url.isLocalFile()) { 0101 installTheme(url.toLocalFile()); 0102 return; 0103 } 0104 0105 if (m_tempCopyJob) { 0106 return; 0107 } 0108 0109 m_tempInstallFile.reset(new QTemporaryFile()); 0110 if (!m_tempInstallFile->open()) { 0111 Q_EMIT showErrorMessage(i18n("Unable to create a temporary file.")); 0112 m_tempInstallFile.reset(); 0113 return; 0114 } 0115 0116 m_tempCopyJob = KIO::file_copy(url, QUrl::fromLocalFile(m_tempInstallFile->fileName()), -1, KIO::Overwrite); 0117 m_tempCopyJob->uiDelegate()->setAutoErrorHandlingEnabled(true); 0118 Q_EMIT downloadingFileChanged(); 0119 0120 connect(m_tempCopyJob, &KIO::FileCopyJob::result, this, [this, url](KJob *job) { 0121 if (job->error() != KJob::NoError) { 0122 Q_EMIT showErrorMessage(i18n("Unable to download the theme: %1", job->errorText())); 0123 return; 0124 } 0125 0126 installTheme(m_tempInstallFile->fileName()); 0127 m_tempInstallFile.reset(); 0128 }); 0129 connect(m_tempCopyJob, &QObject::destroyed, this, &KCMDesktopTheme::downloadingFileChanged); 0130 } 0131 0132 void KCMDesktopTheme::installTheme(const QString &path) 0133 { 0134 qCDebug(KCM_DESKTOP_THEME) << "Installing ... " << path; 0135 0136 const QString program = QStringLiteral("kpackagetool6"); 0137 const QStringList arguments = {QStringLiteral("--type"), QStringLiteral("Plasma/Theme"), QStringLiteral("--install"), path}; 0138 0139 qCDebug(KCM_DESKTOP_THEME) << program << arguments.join(QLatin1Char(' ')); 0140 QProcess *myProcess = new QProcess(this); 0141 connect(myProcess, 0142 static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), 0143 this, 0144 [this](int exitCode, QProcess::ExitStatus exitStatus) { 0145 Q_UNUSED(exitStatus) 0146 if (exitCode == 0) { 0147 Q_EMIT showSuccessMessage(i18n("Theme installed successfully.")); 0148 load(); 0149 } else { 0150 Q_EMIT showErrorMessage(i18n("Theme installation failed.")); 0151 } 0152 }); 0153 0154 connect(myProcess, &QProcess::errorOccurred, this, [this](QProcess::ProcessError e) { 0155 qCWarning(KCM_DESKTOP_THEME) << "Theme installation failed: " << e; 0156 Q_EMIT showErrorMessage(i18n("Theme installation failed.")); 0157 }); 0158 0159 myProcess->start(program, arguments); 0160 } 0161 0162 void KCMDesktopTheme::applyPlasmaTheme(QQuickItem *item, const QString &themeName) 0163 { 0164 if (!item) { 0165 return; 0166 } 0167 0168 KSvg::ImageSet *imageSet = m_themes[themeName]; 0169 if (!imageSet) { 0170 imageSet = new KSvg::ImageSet(themeName, QStringLiteral("plasma/desktoptheme"), this); 0171 m_themes[themeName] = imageSet; 0172 } 0173 0174 Q_FOREACH (KSvg::Svg *svg, item->findChildren<KSvg::Svg *>()) { 0175 auto *frame = qobject_cast<KSvg::FrameSvg *>(svg); 0176 svg->setUsingRenderingCache(false); 0177 if (frame) { 0178 frame->setCacheAllRenderedFrames(false); 0179 } 0180 svg->setImageSet(imageSet); 0181 } 0182 } 0183 0184 void KCMDesktopTheme::load() 0185 { 0186 KQuickManagedConfigModule::load(); 0187 m_model->load(); 0188 m_model->setSelectedTheme(desktopThemeSettings()->name()); 0189 } 0190 0191 void KCMDesktopTheme::save() 0192 { 0193 auto msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"), 0194 QStringLiteral("/org/kde/KWin/BlendChanges"), 0195 QStringLiteral("org.kde.KWin.BlendChanges"), 0196 QStringLiteral("start")); 0197 // Plasma theme changes are known to be slow, so make the blend take a while 0198 msg << 1000; 0199 // This is deliberately blocking so that we ensure Kwin has processed the 0200 // animation start event before we potentially trigger client side changes 0201 QDBusConnection::sessionBus().call(msg); 0202 0203 KQuickManagedConfigModule::save(); 0204 KSvg::ImageSet().setImageSetName(desktopThemeSettings()->name()); 0205 processPendingDeletions(); 0206 } 0207 0208 void KCMDesktopTheme::defaults() 0209 { 0210 KQuickManagedConfigModule::defaults(); 0211 0212 // can this be done more elegantly? 0213 const auto pendingDeletions = m_model->match(m_model->index(0, 0), ThemesModel::PendingDeletionRole, true); 0214 for (const QModelIndex &idx : pendingDeletions) { 0215 m_model->setData(idx, false, ThemesModel::PendingDeletionRole); 0216 } 0217 } 0218 0219 bool KCMDesktopTheme::canEditThemes() const 0220 { 0221 return m_haveThemeExplorerInstalled; 0222 } 0223 0224 void KCMDesktopTheme::editTheme(const QString &theme) 0225 { 0226 QProcess::startDetached(QStringLiteral("plasmathemeexplorer"), {QStringLiteral("-t"), theme}); 0227 } 0228 0229 bool KCMDesktopTheme::isSaveNeeded() const 0230 { 0231 return !m_model->match(m_model->index(0, 0), ThemesModel::PendingDeletionRole, true).isEmpty(); 0232 } 0233 0234 void KCMDesktopTheme::processPendingDeletions() 0235 { 0236 const QString program = QStringLiteral("plasmapkg2"); 0237 0238 const auto pendingDeletions = m_model->match(m_model->index(0, 0), ThemesModel::PendingDeletionRole, true, -1 /*all*/); 0239 QList<QPersistentModelIndex> persistentPendingDeletions; 0240 // turn into persistent model index so we can delete as we go 0241 std::transform(pendingDeletions.begin(), pendingDeletions.end(), std::back_inserter(persistentPendingDeletions), [](const QModelIndex &idx) { 0242 return QPersistentModelIndex(idx); 0243 }); 0244 0245 for (const QPersistentModelIndex &idx : persistentPendingDeletions) { 0246 const QString pluginName = idx.data(ThemesModel::PluginNameRole).toString(); 0247 const QString displayName = idx.data(Qt::DisplayRole).toString(); 0248 0249 Q_ASSERT(pluginName != desktopThemeSettings()->name()); 0250 0251 const QStringList arguments = {QStringLiteral("-t"), QStringLiteral("theme"), QStringLiteral("-r"), pluginName}; 0252 0253 QProcess *process = new QProcess(this); 0254 connect(process, 0255 static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), 0256 this, 0257 [this, process, idx, pluginName, displayName](int exitCode, QProcess::ExitStatus exitStatus) { 0258 Q_UNUSED(exitStatus) 0259 if (exitCode == 0) { 0260 m_model->removeRow(idx.row()); 0261 } else { 0262 Q_EMIT showErrorMessage(i18n("Removing theme failed: %1", QString::fromLocal8Bit(process->readAllStandardOutput().trimmed()))); 0263 m_model->setData(idx, false, ThemesModel::PendingDeletionRole); 0264 } 0265 process->deleteLater(); 0266 }); 0267 0268 process->start(program, arguments); 0269 process->waitForFinished(); // needed so it deletes fine when "OK" is clicked and the dialog destroyed 0270 } 0271 } 0272 0273 #include "kcm.moc"