File indexing completed on 2024-05-05 17:45:25

0001 /*
0002  *  SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
0003  *  SPDX-FileCopyrightText: 2014 Vishesh Handa <me@vhanda.in>
0004  *  SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
0005  *
0006  *  SPDX-License-Identifier: GPL-2.0-or-later
0007  *
0008  */
0009 
0010 #include "kcm.h"
0011 #include "config-kcm.h"
0012 
0013 #include <chrono>
0014 
0015 #include <KAboutData>
0016 #include <KConfigGroup>
0017 #include <KPluginFactory>
0018 #include <KSharedConfig>
0019 #include <QStandardPaths>
0020 
0021 #include <KLocalizedString>
0022 
0023 #include <KAuth/Action>
0024 #include <KAuth/ExecuteJob>
0025 
0026 #include <KNewStuff3/KNSCore/EntryInternal>
0027 
0028 #include <kio/job.h>
0029 
0030 using namespace std::chrono_literals;
0031 
0032 K_PLUGIN_FACTORY_WITH_JSON(KCMPlymouthFactory, "kcm_plymouth.json", registerPlugin<KCMPlymouth>();)
0033 
0034 KCMPlymouth::KCMPlymouth(QObject *parent, const QVariantList &args)
0035     : KQuickAddons::ConfigModule(parent, args)
0036     , m_model(new QStandardItemModel(this))
0037 {
0038     qmlRegisterAnonymousType<QStandardItemModel>("KCMPlymouth", 1);
0039     qmlRegisterAnonymousType<KCMPlymouth>("KCMPlymouth", 1);
0040     KAboutData *about =
0041         new KAboutData(QStringLiteral("kcm_plymouth"), i18n("Boot Splash Screen"), QStringLiteral(PROJECT_VERSION), QString(), KAboutLicense::LGPL);
0042     about->addAuthor(i18n("Marco Martin"), QString(), QStringLiteral("mart@kde.org"));
0043     setAboutData(about);
0044     setButtons(Apply);
0045     setAuthActionName(QStringLiteral("org.kde.kcontrol.kcmplymouth.save"));
0046     setNeedsAuthorization(true);
0047 
0048     m_model->setItemRoleNames({{Qt::DisplayRole, QByteArrayLiteral("display")},
0049                                {DescriptionRole, QByteArrayLiteral("description")},
0050                                {PluginNameRole, QByteArrayLiteral("pluginName")},
0051                                {ScreenhotRole, QByteArrayLiteral("screenshot")},
0052                                {UninstallableRole, QByteArrayLiteral("uninstallable")}});
0053 }
0054 
0055 KCMPlymouth::~KCMPlymouth()
0056 {
0057 }
0058 
0059 void KCMPlymouth::reloadModel()
0060 {
0061     m_model->clear();
0062 
0063     QDir dir(QStringLiteral(PLYMOUTH_THEMES_DIR));
0064     if (!dir.exists()) {
0065         return;
0066     }
0067 
0068     KConfigGroup installedCg(KSharedConfig::openConfig(QStringLiteral("kplymouththemeinstallerrc")), "DownloadedThemes");
0069 
0070     dir.setFilter(QDir::NoDotAndDotDot | QDir::Dirs);
0071 
0072     const auto list = dir.entryInfoList();
0073     for (const QFileInfo &fileInfo : list) {
0074         const QString pluginName = fileInfo.fileName();
0075         QDir themeDir(fileInfo.filePath());
0076 
0077         KConfig file(themeDir.filePath(pluginName + QLatin1String(".plymouth")), KConfig::SimpleConfig);
0078         KConfigGroup grp = file.group("Plymouth Theme");
0079 
0080         QString displayName = grp.readEntry("Name", QString());
0081         if (displayName.isEmpty()) {
0082             displayName = pluginName;
0083         }
0084 
0085         QStandardItem *row = new QStandardItem(displayName);
0086         row->setData(pluginName, PluginNameRole);
0087         row->setData(grp.readEntry("Description", QString()), DescriptionRole);
0088         row->setData(installedCg.entryMap().contains(fileInfo.fileName()), UninstallableRole);
0089 
0090         // the theme has a preview
0091         if (QFile::exists(themeDir.path() + QStringLiteral("/preview.png"))) {
0092             row->setData(QString(themeDir.path() + QStringLiteral("/preview.png")), ScreenhotRole);
0093             // fetch it downloaded from kns
0094         } else {
0095             const QString fileName = installedCg.readEntry(fileInfo.fileName(), QString());
0096             if (fileName.isEmpty()) {
0097                 row->setData(QString(), ScreenhotRole);
0098             } else {
0099                 row->setData(fileName + QStringLiteral(".png"), ScreenhotRole);
0100             }
0101         }
0102 
0103         m_model->appendRow(row);
0104     }
0105 
0106     Q_EMIT selectedPluginIndexChanged();
0107 }
0108 
0109 void KCMPlymouth::onChangedEntriesChanged(const QQmlListReference &changedEntries)
0110 {
0111     static QStringList alreadyCopiedThumbnails;
0112     for (int i = 0; i < changedEntries.count(); ++i) {
0113         auto entry = qobject_cast<KNSCore::EntryWrapper *>(changedEntries.at(i))->entry();
0114         if (entry.isValid() && entry.status() == KNS3::Entry::Installed && !alreadyCopiedThumbnails.contains(entry.uniqueId())) {
0115             alreadyCopiedThumbnails.append(entry.uniqueId());
0116             KIO::file_copy(QUrl(entry.previewUrl(KNSCore::EntryInternal::PreviewBig1)),
0117                            QUrl::fromLocalFile(QString(entry.installedFiles().constFirst() + QStringLiteral(".png"))),
0118                            -1,
0119                            KIO::Overwrite | KIO::HideProgressInfo);
0120         }
0121     }
0122     reloadModel();
0123 }
0124 
0125 QStandardItemModel *KCMPlymouth::themesModel()
0126 {
0127     return m_model;
0128 }
0129 
0130 QString KCMPlymouth::selectedPlugin() const
0131 {
0132     return m_selectedPlugin;
0133 }
0134 
0135 void KCMPlymouth::setSelectedPlugin(const QString &plugin)
0136 {
0137     if (m_selectedPlugin == plugin) {
0138         return;
0139     }
0140 
0141     m_selectedPlugin = plugin;
0142     Q_EMIT selectedPluginChanged();
0143     Q_EMIT selectedPluginIndexChanged();
0144 
0145     setNeedsSave(true);
0146 }
0147 
0148 bool KCMPlymouth::busy() const
0149 {
0150     return m_busy;
0151 }
0152 
0153 void KCMPlymouth::setBusy(const bool &busy)
0154 {
0155     if (m_busy == busy) {
0156         return;
0157     }
0158 
0159     m_busy = busy;
0160     Q_EMIT busyChanged();
0161 }
0162 
0163 int KCMPlymouth::selectedPluginIndex() const
0164 {
0165     for (int i = 0; i < m_model->rowCount(); ++i) {
0166         if (m_model->data(m_model->index(i, 0), PluginNameRole).toString() == m_selectedPlugin) {
0167             return i;
0168         }
0169     }
0170     return -1;
0171 }
0172 
0173 void KCMPlymouth::load()
0174 {
0175     reloadModel();
0176 
0177     KConfigGroup cg(KSharedConfig::openConfig(QStringLiteral(PLYMOUTH_CONFIG_PATH)), "Daemon");
0178 
0179     setSelectedPlugin(cg.readEntry("Theme"));
0180 
0181     setNeedsSave(false);
0182 }
0183 
0184 void KCMPlymouth::save()
0185 {
0186     setBusy(true);
0187     QVariantMap helperargs;
0188     helperargs[QStringLiteral("theme")] = m_selectedPlugin;
0189 
0190     KAuth::Action action(authActionName());
0191     action.setHelperId(QStringLiteral("org.kde.kcontrol.kcmplymouth"));
0192     action.setArguments(helperargs);
0193     // We don't know how long this will take. The helper will need to generate N=installed_kernels initrds.
0194     // Be very generous with the timeout! https://bugs.kde.org/show_bug.cgi?id=400641
0195     // NB: there is also a timeout in the helper
0196     action.setTimeout(std::chrono::milliseconds(15min).count());
0197 
0198     KAuth::ExecuteJob *job = action.execute();
0199     bool rc = job->exec();
0200     if (!rc) {
0201         if (job->error() == KAuth::ActionReply::UserCancelledError) {
0202             Q_EMIT showErrorMessage(i18n("Unable to authenticate/execute the action: %1 (%2)", job->error(), job->errorString()));
0203         }
0204         load();
0205     }
0206     setBusy(false);
0207 }
0208 
0209 void KCMPlymouth::uninstall(const QString &plugin)
0210 {
0211     QVariantMap helperargs;
0212     helperargs[QStringLiteral("theme")] = plugin;
0213 
0214     // KAuth::Action action(authActionName());
0215     KAuth::Action action(QStringLiteral("org.kde.kcontrol.kcmplymouth.uninstall"));
0216     action.setHelperId(QStringLiteral("org.kde.kcontrol.kcmplymouth"));
0217     action.setArguments(helperargs);
0218 
0219     KAuth::ExecuteJob *job = action.execute();
0220     bool rc = job->exec();
0221     if (!rc) {
0222         Q_EMIT showErrorMessage(i18n("Unable to authenticate/execute the action: %1 (%2)", job->error(), job->errorString()));
0223     } else {
0224         KConfigGroup installedCg(KSharedConfig::openConfig(QStringLiteral("kplymouththemeinstallerrc")), "DownloadedThemes");
0225         installedCg.deleteEntry(plugin);
0226         Q_EMIT showSuccessMessage(i18n("Theme uninstalled successfully."));
0227         load();
0228     }
0229 }
0230 
0231 void KCMPlymouth::defaults()
0232 { /*TODO
0233      if (!) {
0234          return;
0235      }
0236  */
0237 }
0238 
0239 #include "kcm.moc"