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 }