Warning, file /plasma/plasma-workspace/kcms/lookandfeel/kcm.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

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 
0008     SPDX-License-Identifier: LGPL-2.0-only
0009 */
0010 
0011 #include "kcm.h"
0012 #include "../kcms-common_p.h"
0013 #include "config-kcm.h"
0014 #include "config-workspace.h"
0015 #include "krdb.h"
0016 
0017 #include <KDialogJobUiDelegate>
0018 #include <KIO/ApplicationLauncherJob>
0019 #include <KIconLoader>
0020 #include <KMessageBox>
0021 #include <KService>
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 <QStyle>
0032 #include <QStyleFactory>
0033 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0034 #include <private/qtx11extras_p.h>
0035 #else
0036 #include <QX11Info>
0037 #endif
0038 
0039 #include <KLocalizedString>
0040 #include <KPackage/PackageLoader>
0041 
0042 #include <array>
0043 
0044 #include <X11/Xlib.h>
0045 
0046 #include <KNSCore/EntryInternal>
0047 #include <QFileInfo>
0048 #include <updatelaunchenvjob.h>
0049 
0050 #ifdef HAVE_XCURSOR
0051 #include "../cursortheme/xcursor/xcursortheme.h"
0052 #include <X11/Xcursor/Xcursor.h>
0053 #endif
0054 
0055 #ifdef HAVE_XFIXES
0056 #include <X11/extensions/Xfixes.h>
0057 #endif
0058 
0059 KCMLookandFeel::KCMLookandFeel(QObject *parent, const KPluginMetaData &data, const QVariantList &args)
0060     : KQuickAddons::ManagedConfigModule(parent, data, args)
0061     , m_lnf(new LookAndFeelManager(this))
0062 {
0063     constexpr char uri[] = "org.kde.private.kcms.lookandfeel";
0064     qmlRegisterAnonymousType<LookAndFeelSettings>("", 1);
0065     qmlRegisterAnonymousType<QStandardItemModel>("", 1);
0066     qmlRegisterUncreatableType<KCMLookandFeel>(uri, 1, 0, "KCMLookandFeel", "Can't create KCMLookandFeel");
0067     qmlRegisterUncreatableType<LookAndFeelManager>(uri, 1, 0, "LookandFeelManager", "Can't create LookandFeelManager");
0068 
0069     setButtons(Default | Help);
0070 
0071     m_model = new QStandardItemModel(this);
0072     QHash<int, QByteArray> roles = m_model->roleNames();
0073     roles[PluginNameRole] = "pluginName";
0074     roles[DescriptionRole] = "description";
0075     roles[ScreenshotRole] = "screenshot";
0076     roles[FullScreenPreviewRole] = "fullScreenPreview";
0077     roles[HasSplashRole] = "hasSplash";
0078     roles[HasLockScreenRole] = "hasLockScreen";
0079     roles[HasRunCommandRole] = "hasRunCommand";
0080     roles[HasLogoutRole] = "hasLogout";
0081     roles[HasGlobalThemeRole] = "hasGlobalTheme"; // For the Global Theme global checkbox
0082     roles[HasLayoutSettingsRole] = "hasLayoutSettings"; // For the Desktop Layout checkbox in More Options
0083     roles[HasDesktopLayoutRole] = "hasDesktopLayout";
0084     roles[HasTitlebarLayoutRole] = "hasTitlebarLayout";
0085     roles[HasColorsRole] = "hasColors";
0086     roles[HasWidgetStyleRole] = "hasWidgetStyle";
0087     roles[HasIconsRole] = "hasIcons";
0088     roles[HasPlasmaThemeRole] = "hasPlasmaTheme";
0089     roles[HasCursorsRole] = "hasCursors";
0090     roles[HasWindowSwitcherRole] = "hasWindowSwitcher";
0091     roles[HasDesktopSwitcherRole] = "hasDesktopSwitcher";
0092     roles[HasWindowDecorationRole] = "hasWindowDecoration";
0093     roles[HasFontsRole] = "hasFonts";
0094 
0095     m_model->setItemRoleNames(roles);
0096     loadModel();
0097 
0098     connect(m_lnf, &LookAndFeelManager::appearanceToApplyChanged, this, &KCMLookandFeel::appearanceToApplyChanged);
0099     connect(m_lnf, &LookAndFeelManager::layoutToApplyChanged, this, &KCMLookandFeel::layoutToApplyChanged);
0100 
0101     connect(m_lnf, &LookAndFeelManager::refreshServices, this, [](const QStringList &toStop, const QList<KService::Ptr> &toStart) {
0102         for (const auto &serviceName : toStop) {
0103             // FIXME: quite ugly way to stop things, and what about non KDE things?
0104             QProcess::startDetached(QStringLiteral("kquitapp5"), {QStringLiteral("--service"), serviceName});
0105         }
0106         for (const auto &service : toStart) {
0107             auto *job = new KIO::ApplicationLauncherJob(service);
0108             job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, nullptr));
0109             job->start();
0110         }
0111     });
0112     connect(m_lnf, &LookAndFeelManager::styleChanged, this, [] {
0113         // FIXME: changing style on the fly breaks QQuickWidgets
0114         notifyKcmChange(GlobalChangeType::StyleChanged);
0115     });
0116     connect(m_lnf, &LookAndFeelManager::colorsChanged, this, [] {
0117         // FIXME: changing style on the fly breaks QQuickWidgets
0118         notifyKcmChange(GlobalChangeType::PaletteChanged);
0119     });
0120     connect(m_lnf, &LookAndFeelManager::iconsChanged, this, [] {
0121         for (int i = 0; i < KIconLoader::LastGroup; i++) {
0122             KIconLoader::emitChange(KIconLoader::Group(i));
0123         }
0124     });
0125     connect(m_lnf, &LookAndFeelManager::cursorsChanged, this, &KCMLookandFeel::cursorsChanged);
0126     connect(m_lnf, &LookAndFeelManager::fontsChanged, this, [] {
0127         QDBusMessage message = QDBusMessage::createSignal("/KDEPlatformTheme", "org.kde.KDEPlatformTheme", "refreshFonts");
0128         QDBusConnection::sessionBus().send(message);
0129     });
0130 }
0131 
0132 KCMLookandFeel::~KCMLookandFeel()
0133 {
0134 }
0135 
0136 void KCMLookandFeel::knsEntryChanged(KNSCore::EntryWrapper *wrapper)
0137 {
0138     if (!wrapper) {
0139         return;
0140     }
0141     const KNSCore::EntryInternal entry = wrapper->entry();
0142     auto removeItemFromModel = [&entry, this]() {
0143         if (entry.uninstalledFiles().isEmpty()) {
0144             return;
0145         }
0146         const QString guessedPluginId = QFileInfo(entry.uninstalledFiles().constFirst()).fileName();
0147         const int index = pluginIndex(guessedPluginId);
0148         if (index != -1) {
0149             m_model->removeRows(index, 1);
0150         }
0151     };
0152     if (entry.status() == KNS3::Entry::Deleted) {
0153         removeItemFromModel();
0154     } else if (entry.status() == KNS3::Entry::Installed && !entry.installedFiles().isEmpty()) {
0155         if (!entry.uninstalledFiles().isEmpty()) {
0156             removeItemFromModel(); // In case we updated it we don't want to have it in twice
0157         }
0158         KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel"));
0159         pkg.setPath(entry.installedFiles().constFirst());
0160         addKPackageToModel(pkg);
0161     }
0162 }
0163 
0164 QStandardItemModel *KCMLookandFeel::lookAndFeelModel() const
0165 {
0166     return m_model;
0167 }
0168 
0169 int KCMLookandFeel::pluginIndex(const QString &pluginName) const
0170 {
0171     const auto results = m_model->match(m_model->index(0, 0), PluginNameRole, pluginName, 1, Qt::MatchExactly);
0172     if (results.count() == 1) {
0173         return results.first().row();
0174     }
0175 
0176     return -1;
0177 }
0178 
0179 QList<KPackage::Package> KCMLookandFeel::availablePackages(const QStringList &components)
0180 {
0181     QList<KPackage::Package> packages;
0182     QStringList paths;
0183     const QStringList dataPaths = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
0184 
0185     paths.reserve(dataPaths.count());
0186     for (const QString &path : dataPaths) {
0187         QDir dir(path + QStringLiteral("/plasma/look-and-feel"));
0188         paths << dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot);
0189     }
0190 
0191     for (const QString &path : paths) {
0192         KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel"));
0193         pkg.setPath(path);
0194         pkg.setFallbackPackage(KPackage::Package());
0195         if (components.isEmpty()) {
0196             packages << pkg;
0197         } else {
0198             for (const auto &component : components) {
0199                 if (!pkg.filePath(component.toUtf8()).isEmpty()) {
0200                     packages << pkg;
0201                     break;
0202                 }
0203             }
0204         }
0205     }
0206 
0207     return packages;
0208 }
0209 
0210 LookAndFeelSettings *KCMLookandFeel::lookAndFeelSettings() const
0211 {
0212     return m_lnf->settings();
0213 }
0214 
0215 void KCMLookandFeel::loadModel()
0216 {
0217     m_model->clear();
0218 
0219     QList<KPackage::Package> pkgs = availablePackages({"defaults", "layouts"});
0220 
0221     // Sort case-insensitively
0222     QCollator collator;
0223     collator.setCaseSensitivity(Qt::CaseInsensitive);
0224     std::sort(pkgs.begin(), pkgs.end(), [&collator](const KPackage::Package &a, const KPackage::Package &b) {
0225         return collator.compare(a.metadata().name(), b.metadata().name()) < 0;
0226     });
0227 
0228     for (const KPackage::Package &pkg : pkgs) {
0229         addKPackageToModel(pkg);
0230     }
0231 
0232     // Model has been cleared so pretend the selected look and fell changed to force view update
0233     Q_EMIT lookAndFeelSettings()->lookAndFeelPackageChanged();
0234 }
0235 
0236 void KCMLookandFeel::addKPackageToModel(const KPackage::Package &pkg)
0237 {
0238     if (!pkg.metadata().isValid()) {
0239         return;
0240     }
0241     QStandardItem *row = new QStandardItem(pkg.metadata().name());
0242     row->setData(pkg.metadata().pluginId(), PluginNameRole);
0243     row->setData(pkg.metadata().description(), DescriptionRole);
0244     row->setData(pkg.filePath("preview"), ScreenshotRole);
0245     row->setData(pkg.filePath("fullscreenpreview"), FullScreenPreviewRole);
0246 
0247     // What the package provides
0248     row->setData(!pkg.filePath("defaults").isEmpty(), HasGlobalThemeRole);
0249     row->setData(!pkg.filePath("layoutdefaults").isEmpty(), HasLayoutSettingsRole);
0250     row->setData(!pkg.filePath("layouts").isEmpty(), HasDesktopLayoutRole);
0251     row->setData(!pkg.filePath("splashmainscript").isEmpty(), HasSplashRole);
0252     row->setData(!pkg.filePath("lockscreenmainscript").isEmpty(), HasLockScreenRole);
0253     row->setData(!pkg.filePath("runcommandmainscript").isEmpty(), HasRunCommandRole);
0254     row->setData(!pkg.filePath("logoutmainscript").isEmpty(), HasLogoutRole);
0255 
0256     if (!pkg.filePath("defaults").isEmpty()) {
0257         KSharedConfigPtr conf = KSharedConfig::openConfig(pkg.filePath("defaults"));
0258         KConfigGroup cg(conf, "kdeglobals");
0259         cg = KConfigGroup(&cg, "General");
0260         bool hasColors = !cg.readEntry("ColorScheme", QString()).isEmpty();
0261         if (!hasColors) {
0262             hasColors = !pkg.filePath("colors").isEmpty();
0263         }
0264         row->setData(hasColors, HasColorsRole);
0265 
0266         cg = KConfigGroup(conf, "kdeglobals");
0267         cg = KConfigGroup(&cg, "KDE");
0268         row->setData(!cg.readEntry("widgetStyle", QString()).isEmpty(), HasWidgetStyleRole);
0269 
0270         cg = KConfigGroup(conf, "kdeglobals");
0271         cg = KConfigGroup(&cg, "Icons");
0272         row->setData(!cg.readEntry("Theme", QString()).isEmpty(), HasIconsRole);
0273 
0274         cg = KConfigGroup(conf, "plasmarc");
0275         cg = KConfigGroup(&cg, "Theme");
0276         row->setData(!cg.readEntry("name", QString()).isEmpty(), HasPlasmaThemeRole);
0277 
0278         cg = KConfigGroup(conf, "kcminputrc");
0279         cg = KConfigGroup(&cg, "Mouse");
0280         row->setData(!cg.readEntry("cursorTheme", QString()).isEmpty(), HasCursorsRole);
0281 
0282         cg = KConfigGroup(conf, "kwinrc");
0283         cg = KConfigGroup(&cg, "WindowSwitcher");
0284         row->setData(!cg.readEntry("LayoutName", QString()).isEmpty(), HasWindowSwitcherRole);
0285 
0286         cg = KConfigGroup(conf, "kwinrc");
0287         cg = KConfigGroup(&cg, "DesktopSwitcher");
0288         row->setData(!cg.readEntry("LayoutName", QString()).isEmpty(), HasDesktopSwitcherRole);
0289 
0290         cg = KConfigGroup(conf, "kwinrc");
0291         cg = KConfigGroup(&cg, "org.kde.kdecoration2");
0292         row->setData(!cg.readEntry("library", QString()).isEmpty() || !cg.readEntry("NoPlugin", QString()).isEmpty(), HasWindowDecorationRole);
0293 
0294         cg = KConfigGroup(conf, "kdeglobals");
0295         KConfigGroup cg2(&cg, "WM"); // for checking activeFont
0296         cg = KConfigGroup(&cg, "General");
0297         row->setData((!cg.readEntry("font", QString()).isEmpty() || !cg.readEntry("fixed", QString()).isEmpty()
0298                       || !cg.readEntry("smallestReadableFont", QString()).isEmpty() || !cg.readEntry("toolBarFont", QString()).isEmpty()
0299                       || !cg.readEntry("menuFont", QString()).isEmpty() || !cg2.readEntry("activeFont", QString()).isEmpty()),
0300                      HasFontsRole);
0301     } else {
0302         // This fallback is needed since the sheet 'breaks' without it
0303         row->setData(false, HasColorsRole);
0304         row->setData(false, HasWidgetStyleRole);
0305         row->setData(false, HasIconsRole);
0306         row->setData(false, HasPlasmaThemeRole);
0307         row->setData(false, HasCursorsRole);
0308         row->setData(false, HasWindowSwitcherRole);
0309         row->setData(false, HasDesktopSwitcherRole);
0310         row->setData(false, HasWindowDecorationRole);
0311         row->setData(false, HasFontsRole);
0312     }
0313     if (!pkg.filePath("layoutdefaults").isEmpty()) {
0314         KSharedConfigPtr conf = KSharedConfig::openConfig(pkg.filePath("layoutdefaults"));
0315         KConfigGroup cg(conf, "kwinrc");
0316         cg = KConfigGroup(&cg, "org.kde.kdecoration2");
0317         row->setData((!cg.readEntry("ButtonsOnLeft", QString()).isEmpty() || !cg.readEntry("ButtonsOnRight", QString()).isEmpty()), HasTitlebarLayoutRole);
0318     } else {
0319         // This fallback is needed since the sheet 'breaks' without it
0320         row->setData(false, HasTitlebarLayoutRole);
0321     }
0322 
0323     m_model->appendRow(row);
0324 }
0325 
0326 bool KCMLookandFeel::isSaveNeeded() const
0327 {
0328     return lookAndFeelSettings()->isSaveNeeded();
0329 }
0330 
0331 void KCMLookandFeel::load()
0332 {
0333     ManagedConfigModule::load();
0334 
0335     m_package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel"), lookAndFeelSettings()->lookAndFeelPackage());
0336 }
0337 
0338 void KCMLookandFeel::save()
0339 {
0340     QString newLnfPackage = lookAndFeelSettings()->lookAndFeelPackage();
0341     KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel"));
0342     package.setPath(newLnfPackage);
0343 
0344     if (!package.isValid()) {
0345         return;
0346     }
0347 
0348     // Disable unavailable flags to prevent unintentional applies
0349     const int index = pluginIndex(lookAndFeelSettings()->lookAndFeelPackage());
0350     auto layoutApplyFlags = m_lnf->layoutToApply();
0351     // Layout Options:
0352     constexpr std::array layoutPairs{
0353         std::make_pair(LookAndFeelManager::DesktopLayout, HasDesktopLayoutRole),
0354         std::make_pair(LookAndFeelManager::TitlebarLayout, HasTitlebarLayoutRole),
0355         std::make_pair(LookAndFeelManager::WindowPlacement, HasDesktopLayoutRole),
0356         std::make_pair(LookAndFeelManager::ShellPackage, HasDesktopLayoutRole),
0357         std::make_pair(LookAndFeelManager::DesktopSwitcher, HasDesktopLayoutRole),
0358     };
0359     for (const auto &pair : layoutPairs) {
0360         if (m_lnf->layoutToApply().testFlag(pair.first)) {
0361             layoutApplyFlags.setFlag(pair.first, m_model->data(m_model->index(index, 0), pair.second).toBool());
0362         }
0363     }
0364     m_lnf->setLayoutToApply(layoutApplyFlags);
0365     // Appearance Options:
0366     auto appearanceApplyFlags = m_lnf->appearanceToApply();
0367     constexpr std::array appearancePairs{
0368         std::make_pair(LookAndFeelManager::Colors, HasColorsRole),
0369         std::make_pair(LookAndFeelManager::WindowDecoration, HasWindowDecorationRole),
0370         std::make_pair(LookAndFeelManager::Icons, HasIconsRole),
0371         std::make_pair(LookAndFeelManager::PlasmaTheme, HasPlasmaThemeRole),
0372         std::make_pair(LookAndFeelManager::Cursors, HasCursorsRole),
0373         std::make_pair(LookAndFeelManager::Fonts, HasFontsRole),
0374         std::make_pair(LookAndFeelManager::WindowSwitcher, HasWindowSwitcherRole),
0375         std::make_pair(LookAndFeelManager::SplashScreen, HasSplashRole),
0376         std::make_pair(LookAndFeelManager::LockScreen, HasLockScreenRole),
0377     };
0378     for (const auto &pair : appearancePairs) {
0379         if (m_lnf->appearanceToApply().testFlag(pair.first)) {
0380             appearanceApplyFlags.setFlag(pair.first, m_model->data(m_model->index(index, 0), pair.second).toBool());
0381         }
0382     }
0383     if (m_lnf->appearanceToApply().testFlag(LookAndFeelManager::WidgetStyle)) {
0384         // Some global themes use styles that may not be installed.
0385         // Test if style can be installed before updating the config.
0386         KSharedConfigPtr conf = KSharedConfig::openConfig(package.filePath("defaults"));
0387         KConfigGroup cg(conf, "kdeglobals");
0388         std::unique_ptr<QStyle> newStyle(QStyleFactory::create(cg.readEntry("widgetStyle", QString())));
0389         appearanceApplyFlags.setFlag(LookAndFeelManager::WidgetStyle,
0390                                      (newStyle != nullptr && m_model->data(m_model->index(index, 0), HasWidgetStyleRole).toBool())); // Widget Style isn't in
0391         // the loop above since it has all of this extra checking too for it
0392     }
0393     m_lnf->setAppearanceToApply(appearanceApplyFlags);
0394 
0395     ManagedConfigModule::save();
0396     m_lnf->save(package, m_package);
0397     m_package.setPath(newLnfPackage);
0398     runRdb(KRdbExportQtColors | KRdbExportGtkTheme | KRdbExportColors | KRdbExportQtSettings | KRdbExportXftSettings);
0399 }
0400 
0401 void KCMLookandFeel::defaults()
0402 {
0403     ManagedConfigModule::defaults();
0404     Q_EMIT showConfirmation();
0405 }
0406 
0407 LookAndFeelManager::AppearanceToApply KCMLookandFeel::appearanceToApply() const
0408 {
0409     return m_lnf->appearanceToApply();
0410 }
0411 
0412 void KCMLookandFeel::setAppearanceToApply(LookAndFeelManager::AppearanceToApply items)
0413 {
0414     m_lnf->setAppearanceToApply(items);
0415 }
0416 
0417 void KCMLookandFeel::resetAppearanceToApply()
0418 {
0419     const int index = pluginIndex(lookAndFeelSettings()->lookAndFeelPackage());
0420     auto applyFlags = appearanceToApply();
0421 
0422     applyFlags.setFlag(LookAndFeelManager::AppearanceSettings, m_model->data(m_model->index(index, 0), HasGlobalThemeRole).toBool());
0423 
0424     m_lnf->setAppearanceToApply(applyFlags); // emits over in lookandfeelmananager
0425 }
0426 
0427 LookAndFeelManager::LayoutToApply KCMLookandFeel::layoutToApply() const
0428 {
0429     return m_lnf->layoutToApply();
0430 }
0431 
0432 void KCMLookandFeel::setLayoutToApply(LookAndFeelManager::LayoutToApply items)
0433 {
0434     m_lnf->setLayoutToApply(items);
0435 }
0436 
0437 void KCMLookandFeel::resetLayoutToApply()
0438 {
0439     const int index = pluginIndex(lookAndFeelSettings()->lookAndFeelPackage());
0440     auto applyFlags = layoutToApply();
0441 
0442     if (m_model->data(m_model->index(index, 0), HasGlobalThemeRole).toBool()) {
0443         m_lnf->setLayoutToApply({}); // Don't enable by default if Global Theme is available
0444         return;
0445     }
0446 
0447     applyFlags.setFlag(LookAndFeelManager::LayoutSettings, m_model->data(m_model->index(index, 0), HasLayoutSettingsRole).toBool());
0448 
0449     m_lnf->setLayoutToApply(applyFlags); // emits over in lookandfeelmananager
0450 }
0451 
0452 QDir KCMLookandFeel::cursorThemeDir(const QString &theme, const int depth)
0453 {
0454     // Prevent infinite recursion
0455     if (depth > 10) {
0456         return QDir();
0457     }
0458 
0459     // Search each icon theme directory for 'theme'
0460     foreach (const QString &baseDir, cursorSearchPaths()) {
0461         QDir dir(baseDir);
0462         if (!dir.exists() || !dir.cd(theme)) {
0463             continue;
0464         }
0465 
0466         // If there's a cursors subdir, we'll assume this is a cursor theme
0467         if (dir.exists(QStringLiteral("cursors"))) {
0468             return dir;
0469         }
0470 
0471         // If the theme doesn't have an index.theme file, it can't inherit any themes.
0472         if (!dir.exists(QStringLiteral("index.theme"))) {
0473             continue;
0474         }
0475 
0476         // Open the index.theme file, so we can get the list of inherited themes
0477         KConfig config(dir.path() + QStringLiteral("/index.theme"), KConfig::NoGlobals);
0478         KConfigGroup cg(&config, "Icon Theme");
0479 
0480         // Recurse through the list of inherited themes, to check if one of them
0481         // is a cursor theme.
0482         const QStringList inherits = cg.readEntry("Inherits", QStringList());
0483         for (const QString &inherit : inherits) {
0484             // Avoid possible DoS
0485             if (inherit == theme) {
0486                 continue;
0487             }
0488 
0489             if (cursorThemeDir(inherit, depth + 1).exists()) {
0490                 return dir;
0491             }
0492         }
0493     }
0494 
0495     return QDir();
0496 }
0497 
0498 QStringList KCMLookandFeel::cursorSearchPaths()
0499 {
0500 #ifdef HAVE_XCURSOR
0501 #if XCURSOR_LIB_MAJOR == 1 && XCURSOR_LIB_MINOR < 1
0502 
0503     if (!m_cursorSearchPaths.isEmpty())
0504         return m_cursorSearchPaths;
0505     // These are the default paths Xcursor will scan for cursor themes
0506     QString path("~/.icons:/usr/share/icons:/usr/share/pixmaps:/usr/X11R6/lib/X11/icons");
0507 
0508     // If XCURSOR_PATH is set, use that instead of the default path
0509     char *xcursorPath = std::getenv("XCURSOR_PATH");
0510     if (xcursorPath)
0511         path = xcursorPath;
0512 #else
0513     // Get the search path from Xcursor
0514     QString path = XcursorLibraryPath();
0515 #endif
0516 
0517     // Separate the paths
0518     m_cursorSearchPaths = path.split(QLatin1Char(':'), Qt::SkipEmptyParts);
0519 
0520     // Remove duplicates
0521     QMutableStringListIterator i(m_cursorSearchPaths);
0522     while (i.hasNext()) {
0523         const QString path = i.next();
0524         QMutableStringListIterator j(i);
0525         while (j.hasNext())
0526             if (j.next() == path)
0527                 j.remove();
0528     }
0529 
0530     // Expand all occurrences of ~/ to the home dir
0531     m_cursorSearchPaths.replaceInStrings(QRegularExpression(QStringLiteral("^~\\/")), QDir::home().path() + QLatin1Char('/'));
0532 #endif
0533     return m_cursorSearchPaths;
0534 }
0535 
0536 void KCMLookandFeel::cursorsChanged(const QString &themeName)
0537 {
0538 #ifdef HAVE_XCURSOR
0539     // Require the Xcursor version that shipped with X11R6.9 or greater, since
0540     // in previous versions the Xfixes code wasn't enabled due to a bug in the
0541     // build system (freedesktop bug #975).
0542 #if defined(HAVE_XFIXES) && XFIXES_MAJOR >= 2 && XCURSOR_LIB_VERSION >= 10105
0543     KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("kcminputrc"));
0544     KConfigGroup cg(config, QStringLiteral("Mouse"));
0545     const int cursorSize = cg.readEntry("cursorSize", 24);
0546 
0547     QDir themeDir = cursorThemeDir(themeName, 0);
0548     if (!themeDir.exists()) {
0549         return;
0550     }
0551 
0552     XCursorTheme theme(themeDir);
0553 
0554     if (!CursorTheme::haveXfixes()) {
0555         return;
0556     }
0557 
0558     UpdateLaunchEnvJob launchEnvJob(QStringLiteral("XCURSOR_THEME"), themeName);
0559 
0560     // Update the Xcursor X resources
0561     runRdb(0);
0562 
0563     // Notify all applications that the cursor theme has changed
0564     notifyKcmChange(GlobalChangeType::CursorChanged);
0565 
0566     // Reload the standard cursors
0567     QStringList names;
0568 
0569     // Qt cursors
0570     names << QStringLiteral("left_ptr") << QStringLiteral("up_arrow") << QStringLiteral("cross") << QStringLiteral("wait") << QStringLiteral("left_ptr_watch")
0571           << QStringLiteral("ibeam") << QStringLiteral("size_ver") << QStringLiteral("size_hor") << QStringLiteral("size_bdiag") << QStringLiteral("size_fdiag")
0572           << QStringLiteral("size_all") << QStringLiteral("split_v") << QStringLiteral("split_h") << QStringLiteral("pointing_hand")
0573           << QStringLiteral("openhand") << QStringLiteral("closedhand") << QStringLiteral("forbidden") << QStringLiteral("whats_this") << QStringLiteral("copy")
0574           << QStringLiteral("move") << QStringLiteral("link");
0575 
0576     // X core cursors
0577     names << QStringLiteral("X_cursor") << QStringLiteral("right_ptr") << QStringLiteral("hand1") << QStringLiteral("hand2") << QStringLiteral("watch")
0578           << QStringLiteral("xterm") << QStringLiteral("crosshair") << QStringLiteral("left_ptr_watch") << QStringLiteral("center_ptr")
0579           << QStringLiteral("sb_h_double_arrow") << QStringLiteral("sb_v_double_arrow") << QStringLiteral("fleur") << QStringLiteral("top_left_corner")
0580           << QStringLiteral("top_side") << QStringLiteral("top_right_corner") << QStringLiteral("right_side") << QStringLiteral("bottom_right_corner")
0581           << QStringLiteral("bottom_side") << QStringLiteral("bottom_left_corner") << QStringLiteral("left_side") << QStringLiteral("question_arrow")
0582           << QStringLiteral("pirate");
0583 
0584     foreach (const QString &name, names) {
0585         XFixesChangeCursorByName(QX11Info::display(), theme.loadCursor(name, cursorSize), QFile::encodeName(name));
0586     }
0587 
0588 #else
0589     KMessageBox::information(this,
0590                              i18n("You have to restart the Plasma session for these changes to take effect."),
0591                              i18n("Cursor Settings Changed"),
0592                              "CursorSettingsChanged");
0593 #endif
0594 #endif
0595 }