File indexing completed on 2024-05-19 05:38:22

0001 /*
0002     SPDX-FileCopyrightText: 2014 Marco Martin <mart@kde.org>
0003     SPDX-FileCopyrightText: 2014 Vishesh Handa <me@vhanda.in>
0004     SPDX-FileCopyrightText: 2019 Cyril Rossi <cyril.rossi@enioka.com>
0005     SPDX-FileCopyrightText: 2021 Benjamin Port <benjamin.port@enioka.com>
0006     SPDX-FileCopyrightText: 2022 Dominic Hayes <ferenosdev@outlook.com>
0007     SPDX-FileCopyrightText: 2023 Ismael Asensio <isma.af@gmail.com>
0008 
0009     SPDX-License-Identifier: LGPL-2.0-only
0010 */
0011 
0012 #include "lookandfeelmanager.h"
0013 #include "../../startkde/plasmaautostart/plasmaautostart.h"
0014 #include "../colors/colorsapplicator.h"
0015 #include "config-kcm.h"
0016 #include "lookandfeeldata.h"
0017 #include "lookandfeelsettings.h"
0018 #include <KIO/CommandLauncherJob>
0019 #include <KPackage/PackageLoader>
0020 #include <KSharedConfig>
0021 #include <QDBusConnection>
0022 #include <QDBusMessage>
0023 #include <QGuiApplication>
0024 #include <QStyle>
0025 #include <QStyleFactory>
0026 
0027 #ifdef HAVE_XCURSOR
0028 #include <X11/Xcursor/Xcursor.h>
0029 #endif
0030 
0031 using namespace Qt::StringLiterals;
0032 
0033 namespace
0034 {
0035 // Helper methods to read or check entries from a nested group within a config
0036 
0037 QString configValue(KSharedConfigPtr config, const QString &groupPath, const QString &entry)
0038 {
0039     // Navigate through the group hierarchy
0040     QStringList groups = groupPath.split(QLatin1Char('/'));
0041     KConfigGroup cg(config, groups.takeAt(0));
0042     for (const QString &group : groups) {
0043         cg = KConfigGroup(&cg, group);
0044     }
0045 
0046     return cg.readEntry(entry, QString());
0047 }
0048 
0049 bool configProvides(KSharedConfigPtr config, const QString &groupPath, const QStringList &entries)
0050 {
0051     return std::any_of(entries.cbegin(), entries.cend(), [config, groupPath](const QString &entry) {
0052         return !configValue(config, groupPath, entry).isEmpty();
0053     });
0054 }
0055 
0056 bool configProvides(KSharedConfigPtr config, const QString &groupPath, const QString &entry)
0057 {
0058     return !configValue(config, groupPath, entry).isEmpty();
0059 }
0060 
0061 } // Anonymouse namespace
0062 
0063 LookAndFeelManager::LookAndFeelManager(QObject *parent)
0064     : QObject(parent)
0065     , m_data(new LookAndFeelData(this))
0066     , m_plasmashellChanged(false)
0067     , m_fontsChanged(false)
0068 {
0069     m_applyLatteLayout = (KService::serviceByDesktopName("org.kde.latte-dock") != nullptr);
0070 }
0071 
0072 LookAndFeelSettings *LookAndFeelManager::settings() const
0073 {
0074     return m_data->settings();
0075 }
0076 
0077 LookAndFeelManager::Contents LookAndFeelManager::packageContents(const KPackage::Package &pkg) const
0078 {
0079     Contents contents = Empty;
0080 
0081     contents.setFlag(SplashScreen, !pkg.filePath("splashmainscript").isEmpty());
0082     contents.setFlag(LockScreen, !pkg.filePath("lockscreenmainscript").isEmpty());
0083 
0084     contents.setFlag(DesktopLayout, !pkg.filePath("layouts").isEmpty());
0085 
0086     // TODO: Those seem unused... are deprecated?
0087     contents.setFlag(RunCommand, !pkg.filePath("runcommandmainscript").isEmpty());
0088     contents.setFlag(LogOutScript, !pkg.filePath("logoutmainscript").isEmpty());
0089 
0090     if (!pkg.filePath("layoutdefaults").isEmpty()) {
0091         KSharedConfigPtr conf = KSharedConfig::openConfig(pkg.filePath("layoutdefaults"));
0092         contents.setFlag(TitlebarLayout, configProvides(conf, "kwinrc/org.kde.kdecoration2", {"ButtonsOnLeft", "ButtonsOnRight"}));
0093     }
0094 
0095     if (!pkg.filePath("defaults").isEmpty()) {
0096         KSharedConfigPtr conf = KSharedConfig::openConfig(pkg.filePath("defaults"));
0097 
0098         contents.setFlag(Colors, configProvides(conf, "kdeglobals/General", "ColorScheme") || !pkg.filePath("colors").isEmpty());
0099         contents.setFlag(WidgetStyle, configProvides(conf, "kdeglobals/KDE", "widgetStyle"));
0100         contents.setFlag(Icons, configProvides(conf, "kdeglobals/Icons", "Theme"));
0101 
0102         contents.setFlag(PlasmaTheme, configProvides(conf, "plasmarc/Theme", "name"));
0103         contents.setFlag(Wallpaper, configProvides(conf, "Wallpaper", "Image"));
0104         contents.setFlag(Cursors, configProvides(conf, "kcminputrc/Mouse", "cursorTheme"));
0105 
0106         contents.setFlag(WindowSwitcher, configProvides(conf, "kwinrc/WindowSwitcher", "LayoutName"));
0107         contents.setFlag(WindowDecoration, configProvides(conf, "kwinrc/org.kde.kdecoration2", {"library", "NoPlugin"}));
0108         contents.setFlag(BorderSize, configProvides(conf, "kwinrc/org.kde.kdecoration2", "BorderSize"));
0109 
0110         contents.setFlag(Fonts,
0111                          configProvides(conf, "kdeglobals/WM", {"font", "fixed", "smallestReadableFont", "toolBarFont", "menuFont"})
0112                              || configProvides(conf, "kdeglobals/General", "activeFont"));
0113 
0114         contents.setFlag(WindowPlacement, configProvides(conf, "kwinrc/Windows", "Placement"));
0115         contents.setFlag(ShellPackage, configProvides(conf, "plasmashellrc/Shell", "ShellPackage"));
0116 
0117         // TODO: Currently managed together within the "DesktopLayout" content
0118         // contents.setFlag(Autostart, configProvides(conf, "Autostart", "Services"));
0119     }
0120 
0121     return contents;
0122 }
0123 
0124 void LookAndFeelManager::setSplashScreen(const QString &theme)
0125 {
0126     if (theme.isEmpty()) {
0127         return;
0128     }
0129 
0130     KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("ksplashrc"));
0131     KConfigGroup group(config, QStringLiteral("KSplash"));
0132 
0133     KConfig configDefault(configDefaults(QStringLiteral("ksplashrc")));
0134     KConfigGroup defaultGroup(&configDefault, QStringLiteral("KSplash"));
0135     writeNewDefaults(group, defaultGroup, QStringLiteral("Theme"), theme);
0136     // TODO: a way to set none as spash in the l&f
0137     writeNewDefaults(group, defaultGroup, QStringLiteral("Engine"), QStringLiteral("KSplashQML"));
0138 }
0139 
0140 void LookAndFeelManager::setLockScreen(const QString &theme)
0141 {
0142     if (theme.isEmpty()) {
0143         return;
0144     }
0145 
0146     writeNewDefaults(QStringLiteral("kscreenlockerrc"), QStringLiteral("Greeter"), QStringLiteral("Theme"), theme);
0147 }
0148 
0149 void LookAndFeelManager::setWindowSwitcher(const QString &theme)
0150 {
0151     if (theme.isEmpty()) {
0152         return;
0153     }
0154 
0155     writeNewDefaults(QStringLiteral("kwinrc"), QStringLiteral("TabBox"), QStringLiteral("LayoutName"), theme);
0156 }
0157 
0158 void LookAndFeelManager::setWindowPlacement(const QString &value)
0159 {
0160     if (value.isEmpty()) {
0161         return;
0162     }
0163 
0164     writeNewDefaults(QStringLiteral("kwinrc"), QStringLiteral("Windows"), QStringLiteral("Placement"), value);
0165 }
0166 
0167 void LookAndFeelManager::setShellPackage(const QString &value)
0168 {
0169     if (value.isEmpty()) {
0170         return;
0171     }
0172 
0173     writeNewDefaults(QStringLiteral("plasmashellrc"), QStringLiteral("Shell"), QStringLiteral("ShellPackage"), value);
0174     m_plasmashellChanged = true;
0175 }
0176 
0177 void LookAndFeelManager::setWindowDecoration(const QString &library, const QString &theme, bool noPlugin)
0178 {
0179     if (library.isEmpty()) {
0180         return;
0181     }
0182 
0183     KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("kwinrc"));
0184     KConfigGroup group(config, QStringLiteral("org.kde.kdecoration2"));
0185 
0186     KConfig configDefault(configDefaults(QStringLiteral("kwinrc")));
0187     KConfigGroup defaultGroup(&configDefault, QStringLiteral("org.kde.kdecoration2"));
0188     writeNewDefaults(group, defaultGroup, QStringLiteral("library"), library);
0189     writeNewDefaults(group, defaultGroup, QStringLiteral("theme"), theme, KConfig::Notify);
0190     writeNewDefaults(group, defaultGroup, QStringLiteral("NoPlugin"), noPlugin ? "true" : "false", KConfig::Notify);
0191 }
0192 
0193 void LookAndFeelManager::setTitlebarLayout(const QString &leftbtns, const QString &rightbtns)
0194 {
0195     if (leftbtns.isEmpty() && rightbtns.isEmpty()) {
0196         return;
0197     }
0198 
0199     writeNewDefaults(QStringLiteral("kwinrc"), QStringLiteral("org.kde.kdecoration2"), QStringLiteral("ButtonsOnLeft"), leftbtns, KConfig::Notify);
0200     writeNewDefaults(QStringLiteral("kwinrc"), QStringLiteral("org.kde.kdecoration2"), QStringLiteral("ButtonsOnRight"), rightbtns, KConfig::Notify);
0201 }
0202 
0203 void LookAndFeelManager::setBorderSize(const QString &size)
0204 {
0205     if (size.isEmpty()) {
0206         return;
0207     }
0208 
0209     writeNewDefaults(QStringLiteral("kwinrc"), QStringLiteral("org.kde.kdecoration2"), QStringLiteral("BorderSize"), size, KConfig::Notify);
0210 }
0211 
0212 void LookAndFeelManager::setBorderlessMaximized(const QString &value)
0213 {
0214     if (value.isEmpty()) { // Turn borderless off for unsupported LNFs to prevent issues
0215         writeNewDefaults(QStringLiteral("kwinrc"), QStringLiteral("Windows"), QStringLiteral("BorderlessMaximizedWindows"), "false", KConfig::Notify);
0216         return;
0217     }
0218 
0219     writeNewDefaults(QStringLiteral("kwinrc"), QStringLiteral("Windows"), QStringLiteral("BorderlessMaximizedWindows"), value, KConfig::Notify);
0220 }
0221 
0222 void LookAndFeelManager::setWidgetStyle(const QString &style)
0223 {
0224     if (style.isEmpty()) {
0225         return;
0226     }
0227 
0228     if (qobject_cast<QGuiApplication *>(QCoreApplication::instance())) {
0229         // Some global themes use styles that may not be installed.
0230         // Test if style can be installed before updating the config.
0231         std::unique_ptr<QStyle> testStyle(QStyleFactory::create(style));
0232         if (!testStyle) {
0233             return;
0234         }
0235     }
0236 
0237     writeNewDefaults(QStringLiteral("kdeglobals"), QStringLiteral("KDE"), QStringLiteral("widgetStyle"), style, KConfig::Notify);
0238     Q_EMIT styleChanged(style);
0239 }
0240 
0241 void LookAndFeelManager::setColors(const QString &scheme, const QString &colorFile)
0242 {
0243     if (scheme.isEmpty() && colorFile.isEmpty()) {
0244         return;
0245     }
0246 
0247     KConfig configDefault(configDefaults(QStringLiteral("kdeglobals")));
0248     auto kdeGlobalsCfg = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::FullConfig);
0249 
0250     if (m_mode == Mode::Apply) {
0251         applyScheme(colorFile, kdeGlobalsCfg.data(), KConfig::Notify);
0252     }
0253 
0254     writeNewDefaults(*kdeGlobalsCfg, configDefault, QStringLiteral("General"), QStringLiteral("ColorScheme"), scheme, KConfig::Notify);
0255 
0256     Q_EMIT colorsChanged();
0257 }
0258 
0259 void LookAndFeelManager::setIcons(const QString &theme)
0260 {
0261     if (theme.isEmpty()) {
0262         return;
0263     }
0264 
0265     writeNewDefaults(QStringLiteral("kdeglobals"), QStringLiteral("Icons"), QStringLiteral("Theme"), theme, KConfig::Notify);
0266 
0267     Q_EMIT iconsChanged();
0268 }
0269 
0270 void LookAndFeelManager::setLatteLayout(const QString &filepath, const QString &name)
0271 {
0272     if (filepath.isEmpty()) {
0273         // there is no latte layout
0274         KIO::CommandLauncherJob latteapp(QStringLiteral("latte-dock"), {QStringLiteral("--disable-autostart")});
0275         latteapp.setDesktopName("org.kde.latte-dock");
0276         latteapp.start();
0277 
0278         QDBusMessage quitmessage = QDBusMessage::createMethodCall(QStringLiteral("org.kde.lattedock"),
0279                                                                   QStringLiteral("/MainApplication"),
0280                                                                   QStringLiteral("org.qtproject.Qt.QCoreApplication"),
0281                                                                   QStringLiteral("quit"));
0282         QDBusConnection::sessionBus().call(quitmessage, QDBus::NoBlock);
0283     } else {
0284         KIO::CommandLauncherJob latteapp(
0285             QStringLiteral("latte-dock"),
0286             {QStringLiteral("--enable-autostart"), QStringLiteral("--import-layout"), filepath, QStringLiteral("--suggested-layout-name"), name});
0287         latteapp.setDesktopName("org.kde.latte-dock");
0288         latteapp.start();
0289     }
0290 }
0291 
0292 void LookAndFeelManager::setPlasmaTheme(const QString &theme)
0293 {
0294     if (theme.isEmpty()) {
0295         return;
0296     }
0297 
0298     writeNewDefaults(QStringLiteral("plasmarc"), QStringLiteral("Theme"), QStringLiteral("name"), theme);
0299 }
0300 
0301 void LookAndFeelManager::setGeneralFont(const QString &font)
0302 {
0303     if (font.isEmpty()) {
0304         return;
0305     }
0306 
0307     writeNewDefaults(QStringLiteral("kdeglobals"), QStringLiteral("General"), QStringLiteral("font"), font, KConfig::Notify);
0308     m_fontsChanged = true;
0309 }
0310 
0311 void LookAndFeelManager::setFixedFont(const QString &font)
0312 {
0313     if (font.isEmpty()) {
0314         return;
0315     }
0316 
0317     writeNewDefaults(QStringLiteral("kdeglobals"), QStringLiteral("General"), QStringLiteral("fixed"), font, KConfig::Notify);
0318     m_fontsChanged = true;
0319 }
0320 
0321 void LookAndFeelManager::setSmallestReadableFont(const QString &font)
0322 {
0323     if (font.isEmpty()) {
0324         return;
0325     }
0326 
0327     writeNewDefaults(QStringLiteral("kdeglobals"), QStringLiteral("General"), QStringLiteral("smallestReadableFont"), font, KConfig::Notify);
0328     m_fontsChanged = true;
0329 }
0330 
0331 void LookAndFeelManager::setToolbarFont(const QString &font)
0332 {
0333     if (font.isEmpty()) {
0334         return;
0335     }
0336 
0337     writeNewDefaults(QStringLiteral("kdeglobals"), QStringLiteral("General"), QStringLiteral("toolBarFont"), font, KConfig::Notify);
0338     m_fontsChanged = true;
0339 }
0340 
0341 void LookAndFeelManager::setMenuFont(const QString &font)
0342 {
0343     if (font.isEmpty()) {
0344         return;
0345     }
0346 
0347     writeNewDefaults(QStringLiteral("kdeglobals"), QStringLiteral("General"), QStringLiteral("menuFont"), font, KConfig::Notify);
0348     m_fontsChanged = true;
0349 }
0350 
0351 void LookAndFeelManager::setWindowTitleFont(const QString &font)
0352 {
0353     if (font.isEmpty()) {
0354         return;
0355     }
0356 
0357     writeNewDefaults(QStringLiteral("kdeglobals"), QStringLiteral("WM"), QStringLiteral("activeFont"), font, KConfig::Notify);
0358     m_fontsChanged = true;
0359 }
0360 
0361 void LookAndFeelManager::writeNewDefaults(const QString &filename,
0362                                           const QString &group,
0363                                           const QString &key,
0364                                           const QString &value,
0365                                           KConfig::WriteConfigFlags writeFlags)
0366 {
0367     KSharedConfigPtr config = KSharedConfig::openConfig(filename);
0368     KConfigGroup configGroup(config, group);
0369 
0370     KConfig configDefault(configDefaults(filename));
0371     KConfigGroup defaultGroup(&configDefault, group);
0372 
0373     writeNewDefaults(configGroup, defaultGroup, key, value, writeFlags);
0374 }
0375 
0376 void LookAndFeelManager::writeNewDefaults(KConfig &config,
0377                                           KConfig &configDefault,
0378                                           const QString &group,
0379                                           const QString &key,
0380                                           const QString &value,
0381                                           KConfig::WriteConfigFlags writeFlags)
0382 {
0383     KConfigGroup configGroup(&config, group);
0384     KConfigGroup defaultGroup(&configDefault, group);
0385 
0386     writeNewDefaults(configGroup, defaultGroup, key, value, writeFlags);
0387 }
0388 
0389 void LookAndFeelManager::writeNewDefaults(KConfigGroup &group,
0390                                           KConfigGroup &defaultGroup,
0391                                           const QString &key,
0392                                           const QString &value,
0393                                           KConfig::WriteConfigFlags writeFlags)
0394 {
0395     defaultGroup.writeEntry(key, value, writeFlags);
0396     defaultGroup.sync();
0397 
0398     if (m_mode == Mode::Apply) {
0399         group.revertToDefault(key, writeFlags);
0400         group.sync();
0401     }
0402 }
0403 
0404 KConfig LookAndFeelManager::configDefaults(const QString &filename)
0405 {
0406     return KConfig(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1String("/kdedefaults/") + filename, KConfig::SimpleConfig);
0407 }
0408 
0409 QString LookAndFeelManager::colorSchemeFile(const QString &schemeName) const
0410 {
0411     QString colorScheme(schemeName);
0412     colorScheme.remove(QLatin1Char('\'')); // So Foo's does not become FooS
0413     QRegularExpression fixer(QStringLiteral("[\\W,.-]+(.?)"));
0414     for (auto match = fixer.match(colorScheme); match.hasMatch(); match = fixer.match(colorScheme)) {
0415         colorScheme.replace(match.capturedStart(), match.capturedLength(), match.captured(1).toUpper());
0416     }
0417     colorScheme.replace(0, 1, colorScheme.at(0).toUpper());
0418 
0419     // NOTE: why this loop trough all the scheme files?
0420     // the scheme theme name is an heuristic, there is no plugin metadata whatsoever.
0421     // is based on the file name stripped from weird characters or the
0422     // eventual id- prefix store.kde.org puts, so we can just find a
0423     // theme that ends as the specified name
0424     const QStringList schemeDirs =
0425         QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("color-schemes"), QStandardPaths::LocateDirectory);
0426     for (const QString &dir : schemeDirs) {
0427         const QStringList fileNames = QDir(dir).entryList(QStringList() << QStringLiteral("*.colors"));
0428         for (const QString &file : fileNames) {
0429             if (file.endsWith(colorScheme + QStringLiteral(".colors"))) {
0430                 return dir + QLatin1Char('/') + file;
0431             }
0432         }
0433     }
0434     return QString();
0435 }
0436 
0437 void LookAndFeelManager::save(const KPackage::Package &package, const KPackage::Package &previousPackage, Contents applyMask)
0438 {
0439     // The items to apply are the package contents filtered with the user selection mask
0440     const Contents itemsToApply = packageContents(package) & applyMask;
0441 
0442     if (itemsToApply.testFlag(DesktopLayout) && m_mode == Mode::Apply) {
0443         QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.plasmashell"),
0444                                                               QStringLiteral("/PlasmaShell"),
0445                                                               QStringLiteral("org.kde.PlasmaShell"),
0446                                                               QStringLiteral("loadLookAndFeelDefaultLayout"));
0447 
0448         QList<QVariant> args;
0449         args << m_data->settings()->lookAndFeelPackage();
0450         message.setArguments(args);
0451 
0452         QDBusConnection::sessionBus().call(message, QDBus::NoBlock);
0453 
0454         if (m_applyLatteLayout) {
0455             //! latte exists in system and user has chosen to update desktop layout
0456             setLatteLayout(package.filePath("layouts", "looknfeel.layout.latte"), package.metadata().name());
0457         }
0458     }
0459 
0460     if (!package.filePath("layoutdefaults").isEmpty()) {
0461         KSharedConfigPtr conf = KSharedConfig::openConfig(package.filePath("layoutdefaults"));
0462         KConfigGroup group(conf, u"kwinrc"_s);
0463         if (itemsToApply.testFlag(TitlebarLayout)) {
0464             group = KConfigGroup(&group, u"org.kde.kdecoration2"_s);
0465             setTitlebarLayout(group.readEntry("ButtonsOnLeft", QString()), group.readEntry("ButtonsOnRight", QString()));
0466         }
0467         if (itemsToApply.testFlag(DesktopLayout) && m_mode == Mode::Apply) {
0468             group = KConfigGroup(conf, u"kwinrc"_s);
0469             group = KConfigGroup(&group, u"Windows"_s);
0470             setBorderlessMaximized(group.readEntry("BorderlessMaximizedWindows", QString()));
0471         }
0472     }
0473     if (!package.filePath("defaults").isEmpty()) {
0474         KSharedConfigPtr conf = KSharedConfig::openConfig(package.filePath("defaults"));
0475         KConfigGroup group(conf, u"kdeglobals"_s);
0476         group = KConfigGroup(&group, u"KDE"_s);
0477         if (itemsToApply.testFlag(WidgetStyle)) {
0478             QString widgetStyle = group.readEntry("widgetStyle", QString());
0479             // Some global themes refer to breeze's widgetStyle with a lowercase b.
0480             if (widgetStyle == QStringLiteral("breeze")) {
0481                 widgetStyle = QStringLiteral("Breeze");
0482             }
0483 
0484             setWidgetStyle(widgetStyle);
0485         }
0486 
0487         if (itemsToApply.testFlag(Colors)) {
0488             QString colorsFile = package.filePath("colors");
0489             KConfigGroup group(conf, u"kdeglobals"_s);
0490             group = KConfigGroup(&group, u"General"_s);
0491             QString colorScheme = group.readEntry("ColorScheme", QString());
0492 
0493             if (!colorsFile.isEmpty()) {
0494                 if (!colorScheme.isEmpty()) {
0495                     setColors(colorScheme, colorsFile);
0496                 } else {
0497                     setColors(package.metadata().name(), colorsFile);
0498                 }
0499             } else if (!colorScheme.isEmpty()) {
0500                 QString path = colorSchemeFile(colorScheme);
0501                 if (!path.isEmpty()) {
0502                     setColors(colorScheme, path);
0503                 }
0504             }
0505         }
0506 
0507         if (itemsToApply.testFlag(Icons)) {
0508             group = KConfigGroup(conf, u"kdeglobals"_s);
0509             group = KConfigGroup(&group, u"Icons"_s);
0510             setIcons(group.readEntry("Theme", QString()));
0511         }
0512 
0513         if (itemsToApply.testFlag(PlasmaTheme)) {
0514             group = KConfigGroup(conf, u"plasmarc"_s);
0515             group = KConfigGroup(&group, u"Theme"_s);
0516             setPlasmaTheme(group.readEntry("name", QString()));
0517         }
0518 
0519         if (itemsToApply.testFlag(Cursors)) {
0520             group = KConfigGroup(conf, u"kcminputrc"_s);
0521             group = KConfigGroup(&group, u"Mouse"_s);
0522             setCursorTheme(group.readEntry("cursorTheme", QString()));
0523         }
0524 
0525         if (itemsToApply.testFlag(WindowSwitcher)) {
0526             group = KConfigGroup(conf, u"kwinrc"_s);
0527             group = KConfigGroup(&group, u"WindowSwitcher"_s);
0528             setWindowSwitcher(group.readEntry("LayoutName", QString()));
0529         }
0530 
0531         if (itemsToApply.testFlag(WindowPlacement)) {
0532             group = KConfigGroup(conf, u"kwinrc"_s);
0533             group = KConfigGroup(&group, u"Windows"_s);
0534             setWindowPlacement(group.readEntry("Placement", QStringLiteral("Centered")));
0535         }
0536 
0537         if (itemsToApply.testFlag(ShellPackage)) {
0538             group = KConfigGroup(conf, u"plasmashellrc"_s);
0539             group = KConfigGroup(&group, u"Shell"_s);
0540             setShellPackage(group.readEntry("ShellPackage", QString()));
0541         }
0542 
0543         if (itemsToApply.testFlag(WindowDecoration)) {
0544             group = KConfigGroup(conf, u"kwinrc"_s);
0545             group = KConfigGroup(&group, u"org.kde.kdecoration2"_s);
0546 
0547 #ifdef HAVE_BREEZE_DECO
0548             setWindowDecoration(group.readEntry("library", QStringLiteral(BREEZE_KDECORATION_PLUGIN_ID)),
0549                                 group.readEntry("theme", QStringLiteral("Breeze")),
0550                                 group.readEntry("NoPlugin", false));
0551 #else
0552             setWindowDecoration(group.readEntry("library", QStringLiteral("org.kde.kwin.aurorae")),
0553                                 group.readEntry("theme", QStringLiteral("kwin4_decoration_qml_plastik")),
0554                                 group.readEntry("NoPlugin", false));
0555 #endif
0556         }
0557 
0558         if (itemsToApply.testFlag(Fonts)) {
0559             group = KConfigGroup(conf, u"kdeglobals"_s);
0560             group = KConfigGroup(&group, u"General"_s);
0561             setGeneralFont(group.readEntry("font", QString()));
0562             setFixedFont(group.readEntry("fixed", QString()));
0563             setSmallestReadableFont(group.readEntry("smallestReadableFont", QString()));
0564             setToolbarFont(group.readEntry("toolBarFont", QString()));
0565             setMenuFont(group.readEntry("menuFont", QString()));
0566             group = KConfigGroup(conf, u"kdeglobals"_s);
0567             group = KConfigGroup(&group, u"WM"_s);
0568             setWindowTitleFont(group.readEntry("activeFont"));
0569             if (m_fontsChanged) {
0570                 Q_EMIT fontsChanged();
0571                 m_fontsChanged = false;
0572             }
0573         }
0574 
0575         if (itemsToApply.testFlag(BorderSize)) {
0576             group = KConfigGroup(conf, u"kwinrc"_s);
0577             group = KConfigGroup(&group, u"org.kde.kdecoration2"_s);
0578             setBorderSize(group.readEntry("BorderSize", QString()));
0579         }
0580 
0581         if (itemsToApply.testFlag(SplashScreen)) {
0582             group = KConfigGroup(conf, u"ksplashrc"_s);
0583             group = KConfigGroup(&group, u"KSplash"_s);
0584             QString splashScreen = (group.readEntry("Theme", QString()));
0585             if (!splashScreen.isEmpty()) {
0586                 setSplashScreen(splashScreen);
0587             } else {
0588                 setSplashScreen(m_data->settings()->lookAndFeelPackage());
0589             }
0590         }
0591         if (itemsToApply.testFlag(LockScreen)) {
0592             setLockScreen(m_data->settings()->lookAndFeelPackage());
0593         }
0594 
0595         QFile packageFile(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1String("/kdedefaults/package"));
0596         packageFile.open(QIODevice::WriteOnly);
0597         packageFile.write(m_data->settings()->lookAndFeelPackage().toUtf8());
0598 
0599         if (m_mode == Mode::Defaults) {
0600             return;
0601         }
0602 
0603         if (m_plasmashellChanged) {
0604             QDBusMessage message =
0605                 QDBusMessage::createSignal(QStringLiteral("/PlasmaShell"), QStringLiteral("org.kde.PlasmaShell"), QStringLiteral("refreshCurrentShell"));
0606             QDBusConnection::sessionBus().send(message);
0607         }
0608 
0609         // autostart
0610         if (itemsToApply.testFlag(DesktopLayout)) {
0611             QStringList toStop;
0612             KService::List toStart;
0613             // remove all the old package to autostart
0614             {
0615                 KSharedConfigPtr oldConf = KSharedConfig::openConfig(previousPackage.filePath("defaults"));
0616                 group = KConfigGroup(oldConf, u"Autostart"_s);
0617                 const QStringList autostartServices = group.readEntry("Services", QStringList());
0618 
0619                 if (qEnvironmentVariableIsSet("KDE_FULL_SESSION")) {
0620                     for (const QString &serviceFile : autostartServices) {
0621                         KService service(serviceFile + QStringLiteral(".desktop"));
0622                         PlasmaAutostart as(serviceFile);
0623                         as.setAutostarts(false);
0624                         QString serviceName = service.property<QString>(QStringLiteral("X-DBUS-ServiceName"));
0625                         toStop.append(serviceName);
0626                     }
0627                 }
0628             }
0629             // Set all the stuff in the new lnf to autostart
0630             {
0631                 group = KConfigGroup(conf, u"Autostart"_s);
0632                 const QStringList autostartServices = group.readEntry("Services", QStringList());
0633 
0634                 for (const QString &serviceFile : autostartServices) {
0635                     KService::Ptr service(new KService(serviceFile + QStringLiteral(".desktop")));
0636                     PlasmaAutostart as(serviceFile);
0637                     as.setCommand(service->exec());
0638                     as.setAutostarts(true);
0639                     const QString serviceName = service->property<QString>(QStringLiteral("X-DBUS-ServiceName"));
0640                     toStop.removeAll(serviceName);
0641                     if (qEnvironmentVariableIsSet("KDE_FULL_SESSION")) {
0642                         toStart += service;
0643                     }
0644                 }
0645             }
0646             Q_EMIT refreshServices(toStop, toStart);
0647         }
0648     }
0649     // Reload KWin if something changed, but only once.
0650     if (itemsToApply & KWinSettings) {
0651         QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KWin"), QStringLiteral("org.kde.KWin"), QStringLiteral("reloadConfig"));
0652         QDBusConnection::sessionBus().send(message);
0653     }
0654 }
0655 
0656 bool LookAndFeelManager::remove(const KPackage::Package &package, LookAndFeelManager::Contents contentsMask)
0657 {
0658     QDir packageRootDir(package.path());
0659     if (!packageRootDir.isReadable()) {
0660         // Permission denied
0661         return false;
0662     }
0663 
0664     const Contents itemsToRemove = packageContents(package) & contentsMask;
0665 
0666     // Remove package dependencies
0667     auto config = KSharedConfig::openConfig(package.filePath("defaults"), KConfig::NoGlobals);
0668 
0669     // WidgetStyle
0670     if (itemsToRemove.testFlag(WidgetStyle)) {
0671         const QString widgetStyle = configValue(config, "kdeglobals/KDE", "widgetStyle");
0672 
0673         const QDir widgetStyleDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QDir::separator() + QStringLiteral("kstyle")
0674                                   + QDir::separator() + QStringLiteral("themes"));
0675         if (widgetStyleDir.exists()) {
0676             // Read config to get style name
0677             const QStringList styleFileList = widgetStyleDir.entryList({QStringLiteral("*.themerc")}, QDir::Files | QDir::NoDotAndDotDot);
0678             for (const QString &path : styleFileList) {
0679                 auto widgetStyleConfig = KSharedConfig::openConfig(widgetStyleDir.absoluteFilePath(path), KConfig::SimpleConfig);
0680                 KConfigGroup widgetStyleKDEGroup(widgetStyleConfig, u"KDE"_s);
0681                 if (widgetStyleKDEGroup.exists() && widgetStyleKDEGroup.hasKey("WidgetStyle")) {
0682                     if (widgetStyleKDEGroup.readEntry("WidgetStyle", "") == widgetStyle) {
0683                         QFile(widgetStyleDir.absoluteFilePath(path)).remove();
0684                         break;
0685                     }
0686                 }
0687             }
0688         }
0689     }
0690 
0691     // Color scheme
0692     if (itemsToRemove.testFlag(Colors)) {
0693         const QString colorScheme = configValue(config, "kdeglobals/General", "ColorScheme");
0694         QFile schemeFile(colorSchemeFile(colorScheme));
0695         if (schemeFile.exists()) {
0696             schemeFile.remove();
0697         }
0698     }
0699 
0700     // Desktop Theme and Icon (Icons are in the theme folder). Only remove if both selected
0701     // TODO: Remove separately
0702     if (itemsToRemove.testFlag(PlasmaTheme) && itemsToRemove.testFlag(Icons)) {
0703         const QString themeName = configValue(config, "plasmarc/Theme", "name");
0704         QDir themeDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QDir::separator() + QStringLiteral("plasma") + QDir::separator()
0705                       + QStringLiteral("desktoptheme") + QDir::separator() + themeName);
0706         if (themeDir.exists()) {
0707             themeDir.removeRecursively();
0708         }
0709     }
0710 
0711     // Wallpaper
0712     if (itemsToRemove.testFlag(Wallpaper)) {
0713         const QString image = configValue(config, "Wallpaper", "Image");
0714         const QDir wallpaperDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QDir::separator() + QStringLiteral("wallpapers"));
0715         if (wallpaperDir.exists()) {
0716             QFileInfo wallpaperFile(wallpaperDir.absoluteFilePath(image));
0717             if (wallpaperFile.isFile()) {
0718                 // Single file, use QFile to remove
0719                 QFile(wallpaperFile.absoluteFilePath()).remove();
0720             } else if (wallpaperFile.isDir()) {
0721                 // A package, use QDir to remove the folder
0722                 QDir(wallpaperFile.absoluteFilePath()).removeRecursively();
0723             }
0724         }
0725     }
0726 
0727     if (itemsToRemove.testFlag(WindowSwitcher)) {
0728         const QString layoutName = configValue(config, "kwinrc/WindowSwitcher", "LayoutName");
0729         const auto windowSwitcherPackages =
0730             KPackage::PackageLoader::self()->findPackages(QStringLiteral("KWin/WindowSwitcher"), QString(), [&layoutName](const KPluginMetaData &meta) {
0731                 return meta.pluginId() == layoutName;
0732             });
0733         for (const auto &meta : windowSwitcherPackages) {
0734             QFileInfo(meta.fileName()).absoluteDir().removeRecursively();
0735         }
0736     }
0737 
0738     return packageRootDir.removeRecursively();
0739 }
0740 
0741 void LookAndFeelManager::setCursorTheme(const QString themeName)
0742 {
0743     // TODO: use pieces of cursor kcm when moved to plasma-desktop
0744     if (themeName.isEmpty()) {
0745         return;
0746     }
0747 
0748     writeNewDefaults(QStringLiteral("kcminputrc"), QStringLiteral("Mouse"), QStringLiteral("cursorTheme"), themeName, KConfig::Notify);
0749     Q_EMIT cursorsChanged(themeName);
0750 }
0751 
0752 void LookAndFeelManager::setMode(Mode mode)
0753 {
0754     m_mode = mode;
0755 }