File indexing completed on 2024-04-28 16:44:24
0001 /* 0002 SPDX-FileCopyrightText: 1999 Matthias Hoelzer-Kluepfel <hoelzer@kde.org> 0003 SPDX-FileCopyrightText: 2000 Matthias Elter <elter@kde.org> 0004 SPDX-FileCopyrightText: 2004 Frans Englich <frans.englich@telia.com> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 0008 */ 0009 0010 #include "main.h" 0011 0012 #include <config-kde-cli-tools.h> 0013 0014 #include <QCommandLineOption> 0015 #include <QCommandLineParser> 0016 #include <QDBusConnection> 0017 #include <QDBusConnectionInterface> 0018 #include <QDBusInterface> 0019 #include <QDBusServiceWatcher> 0020 #include <QDebug> 0021 #include <QIcon> 0022 #include <QRegularExpression> 0023 #include <QStandardPaths> 0024 0025 #include <KAboutData> 0026 #include <KActivities/ResourceInstance> 0027 #include <KAuthorized> 0028 #include <KCModuleProxy> 0029 #include <KLocalizedString> 0030 #include <KPluginMetaData> 0031 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 92) 0032 #include <KServiceTypeTrader> 0033 #endif 0034 #include <KStartupInfo> 0035 #include <kworkspace.h> 0036 0037 #include <algorithm> 0038 #include <iostream> 0039 0040 inline QVector<KPluginMetaData> findKCMsMetaData() 0041 { 0042 QVector<KPluginMetaData> metaDataList = KPluginMetaData::findPlugins(QStringLiteral("plasma/kcms")); 0043 metaDataList << KPluginMetaData::findPlugins(QStringLiteral("plasma/kcms/systemsettings")); 0044 metaDataList << KPluginMetaData::findPlugins(QStringLiteral("plasma/kcms/systemsettings_qwidgets")); 0045 metaDataList << KPluginMetaData::findPlugins(QStringLiteral("plasma/kcms/kinfocenter")); 0046 return metaDataList; 0047 } 0048 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 92) 0049 static KService::List listModules() 0050 { 0051 // First condition is what systemsettings does, second what kinfocenter does, make sure this is kept in sync 0052 // We need the exist calls because otherwise the trader language aborts if the property doesn't exist and the second part of the or is not evaluated 0053 KService::List services = 0054 KServiceTypeTrader::self()->query(QStringLiteral("KCModule"), 0055 QStringLiteral("(exist [X-KDE-System-Settings-Parent-Category] and [X-KDE-System-Settings-Parent-Category] != '') or " 0056 "(exist [X-KDE-ParentApp] and [X-KDE-ParentApp] == 'kinfocenter')")); 0057 0058 auto it = std::remove_if(services.begin(), services.end(), [](const KService::Ptr &service) { 0059 return !KAuthorized::authorizeControlModule(service->menuId()); 0060 }); 0061 services.erase(it, services.end()); 0062 0063 std::stable_sort(services.begin(), services.end(), [](const KService::Ptr s1, const KService::Ptr s2) { 0064 return QString::compare(s1->desktopEntryName(), s2->desktopEntryName(), Qt::CaseInsensitive) < 0; 0065 }); 0066 0067 return services; 0068 } 0069 0070 static KService::Ptr locateModule(const QString &module) 0071 { 0072 QString path = module; 0073 0074 if (!path.endsWith(QLatin1String(".desktop"))) { 0075 path += QStringLiteral(".desktop"); 0076 } 0077 0078 KService::Ptr service = KService::serviceByStorageId(path); 0079 if (!service) { 0080 return KService::Ptr(); 0081 } 0082 0083 if (!service->hasServiceType(QStringLiteral("KCModule"))) { 0084 // Not a KCModule. E.g. "kcmshell5 akonadi" finds services/kresources/kabc/akonadi.desktop, unrelated. 0085 return KService::Ptr(); 0086 } 0087 0088 if (service->noDisplay()) { 0089 qDebug() << module << "should not be loaded."; 0090 return KService::Ptr(); 0091 } 0092 0093 return service; 0094 } 0095 #endif 0096 0097 bool KCMShell::isRunning() 0098 { 0099 const QString owner = QDBusConnection::sessionBus().interface()->serviceOwner(m_serviceName); 0100 if (owner == QDBusConnection::sessionBus().baseService()) { 0101 return false; // We are the one and only. 0102 } 0103 0104 qDebug() << "kcmshell5 with modules" << m_serviceName << "is already running."; 0105 0106 QDBusInterface iface(m_serviceName, QStringLiteral("/KCModule/dialog"), QStringLiteral("org.kde.KCMShellMultiDialog")); 0107 QDBusReply<void> reply = iface.call(QStringLiteral("activate"), KStartupInfo::startupId()); 0108 if (!reply.isValid()) { 0109 qDebug() << "Calling D-Bus function dialog::activate() failed."; 0110 return false; // Error, we have to do it ourselves. 0111 } 0112 0113 return true; 0114 } 0115 0116 KCMShellMultiDialog::KCMShellMultiDialog(KPageDialog::FaceType dialogFace, QWidget *parent) 0117 : KCMultiDialog(parent) 0118 { 0119 setFaceType(dialogFace); 0120 setModal(false); 0121 0122 QDBusConnection::sessionBus().registerObject(QStringLiteral("/KCModule/dialog"), this, QDBusConnection::ExportScriptableSlots); 0123 0124 connect(this, &KCMShellMultiDialog::currentPageChanged, this, [](KPageWidgetItem *newPage, KPageWidgetItem *oldPage) { 0125 Q_UNUSED(oldPage); 0126 KCModuleProxy *activeModule = newPage->widget()->findChild<KCModuleProxy *>(); 0127 if (activeModule) { 0128 KActivities::ResourceInstance::notifyAccessed(QUrl(QLatin1String("kcm:") + activeModule->metaData().pluginId()), 0129 QStringLiteral("org.kde.systemsettings")); 0130 } 0131 }); 0132 } 0133 0134 void KCMShellMultiDialog::activate(const QByteArray &asn_id) 0135 { 0136 #ifdef HAVE_X11 0137 setAttribute(Qt::WA_NativeWindow, true); 0138 KStartupInfo::setNewStartupId(windowHandle(), asn_id); 0139 #endif 0140 } 0141 0142 void KCMShell::setServiceName(const QString &dbusName) 0143 { 0144 m_serviceName = QLatin1String("org.kde.kcmshell_") + dbusName; 0145 QDBusConnection::sessionBus().registerService(m_serviceName); 0146 } 0147 0148 void KCMShell::waitForExit() 0149 { 0150 QDBusServiceWatcher *watcher = new QDBusServiceWatcher(this); 0151 watcher->setConnection(QDBusConnection::sessionBus()); 0152 watcher->setWatchMode(QDBusServiceWatcher::WatchForOwnerChange); 0153 watcher->addWatchedService(m_serviceName); 0154 connect(watcher, &QDBusServiceWatcher::serviceOwnerChanged, this, &KCMShell::appExit); 0155 exec(); 0156 } 0157 0158 void KCMShell::appExit(const QString &appId, const QString &oldName, const QString &newName) 0159 { 0160 Q_UNUSED(appId); 0161 Q_UNUSED(newName); 0162 0163 if (!oldName.isEmpty()) { 0164 qDebug() << appId << "closed, quitting."; 0165 qApp->quit(); 0166 } 0167 } 0168 0169 int main(int _argc, char *_argv[]) 0170 { 0171 const bool qpaVariable = qEnvironmentVariableIsSet("QT_QPA_PLATFORM"); 0172 KWorkSpace::detectPlatform(_argc, _argv); 0173 KCMShell app(_argc, _argv); 0174 if (!qpaVariable) { 0175 // don't leak the env variable to processes we start 0176 qunsetenv("QT_QPA_PLATFORM"); 0177 } 0178 KLocalizedString::setApplicationDomain("kcmshell5"); 0179 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0180 app.setAttribute(Qt::AA_UseHighDpiPixmaps, true); 0181 #endif 0182 KAboutData aboutData(QStringLiteral("kcmshell5"), // 0183 i18n("System Settings Module"), 0184 QLatin1String(PROJECT_VERSION), 0185 i18n("A tool to start single system settings modules"), 0186 KAboutLicense::GPL, 0187 i18n("(c) 1999-2016, The KDE Developers")); 0188 0189 aboutData.addAuthor(i18n("Frans Englich"), i18n("Maintainer"), QStringLiteral("frans.englich@kde.org")); 0190 aboutData.addAuthor(i18n("Daniel Molkentin"), QString(), QStringLiteral("molkentin@kde.org")); 0191 aboutData.addAuthor(i18n("Matthias Hoelzer-Kluepfel"), QString(), QStringLiteral("hoelzer@kde.org")); 0192 aboutData.addAuthor(i18n("Matthias Elter"), QString(), QStringLiteral("elter@kde.org")); 0193 aboutData.addAuthor(i18n("Matthias Ettrich"), QString(), QStringLiteral("ettrich@kde.org")); 0194 aboutData.addAuthor(i18n("Waldo Bastian"), QString(), QStringLiteral("bastian@kde.org")); 0195 KAboutData::setApplicationData(aboutData); 0196 0197 QCommandLineParser parser; 0198 aboutData.setupCommandLine(&parser); 0199 0200 parser.addOption(QCommandLineOption(QStringLiteral("list"), i18n("List all possible modules"))); 0201 parser.addPositionalArgument(QStringLiteral("module"), i18n("Configuration module to open")); 0202 parser.addOption(QCommandLineOption(QStringLiteral("lang"), i18n("Specify a particular language"), QLatin1String("language"))); 0203 parser.addOption(QCommandLineOption(QStringLiteral("silent"), i18n("Do not display main window"))); 0204 parser.addOption(QCommandLineOption(QStringLiteral("args"), i18n("Arguments for the module"), QLatin1String("arguments"))); 0205 parser.addOption(QCommandLineOption(QStringLiteral("icon"), i18n("Use a specific icon for the window"), QLatin1String("icon"))); 0206 parser.addOption(QCommandLineOption(QStringLiteral("caption"), i18n("Use a specific caption for the window"), QLatin1String("caption"))); 0207 0208 parser.parse(app.arguments()); 0209 aboutData.processCommandLine(&parser); 0210 0211 parser.process(app); 0212 0213 const QString lang = parser.value(QStringLiteral("lang")); 0214 if (!lang.isEmpty()) { 0215 std::cout << i18n("--lang is deprecated. Please set the LANGUAGE environment variable instead").toLocal8Bit().constData() << std::endl; 0216 } 0217 0218 if (parser.isSet(QStringLiteral("list"))) { 0219 std::cout << i18n("The following modules are available:").toLocal8Bit().constData() << '\n'; 0220 0221 QVector<KPluginMetaData> plugins = findKCMsMetaData(); 0222 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 92) 0223 const KService::List services = listModules(); 0224 for (const auto &service : services) { 0225 const QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("/kservices5/") + service->entryPath()); 0226 plugins << KPluginMetaData::fromDesktopFile(file); 0227 } 0228 #endif 0229 int maxLen = 0; 0230 0231 for (const auto &plugin : plugins) { 0232 const int len = plugin.pluginId().size(); 0233 maxLen = std::max(maxLen, len); 0234 } 0235 0236 for (const auto &plugin : plugins) { 0237 QString comment = plugin.description(); 0238 if (comment.isEmpty()) { 0239 comment = i18n("No description available"); 0240 } 0241 0242 const QString entry = QStringLiteral("%1 - %2").arg(plugin.pluginId().leftJustified(maxLen, QLatin1Char(' ')), comment); 0243 0244 std::cout << entry.toLocal8Bit().constData() << '\n'; 0245 } 0246 0247 std::cout << std::endl; 0248 0249 return 0; 0250 } 0251 0252 if (parser.positionalArguments().isEmpty()) { 0253 parser.showHelp(); 0254 return -1; 0255 } 0256 0257 QString serviceName; 0258 QList<KPluginMetaData> metaDataList; 0259 0260 QStringList args = parser.positionalArguments(); 0261 args.removeDuplicates(); 0262 for (const QString &arg : args) { 0263 KPluginMetaData data(arg); 0264 if (data.isValid()) { 0265 metaDataList << data; 0266 } else { 0267 // Look in the namespaces for systemsettings/kinfocenter 0268 const static auto knownKCMs = findKCMsMetaData(); 0269 const QStringList possibleIds{arg, QStringLiteral("kcm_") + arg, QStringLiteral("kcm") + arg}; 0270 bool foundKCM = std::any_of(knownKCMs.begin(), knownKCMs.end(), [&possibleIds, &metaDataList, &arg, &serviceName](const KPluginMetaData &data) { 0271 bool idMatches = possibleIds.contains(data.pluginId()); 0272 if (idMatches) { 0273 metaDataList << data; 0274 if (!serviceName.isEmpty()) { 0275 serviceName += QLatin1Char('_'); 0276 } 0277 serviceName += arg; 0278 } 0279 return idMatches; 0280 }); 0281 if (foundKCM) { 0282 continue; 0283 } 0284 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 92) 0285 KService::Ptr service = locateModule(arg); 0286 if (!service) { 0287 service = locateModule(QStringLiteral("kcm_") + arg); 0288 } 0289 if (!service) { 0290 service = locateModule(QStringLiteral("kcm") + arg); 0291 } 0292 0293 if (service) { 0294 if (!serviceName.isEmpty()) { 0295 serviceName += QLatin1Char('_'); 0296 } 0297 serviceName += arg; 0298 0299 const QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("/kservices5/") + service->entryPath()); 0300 auto data = KPluginMetaData::fromDesktopFile(file); 0301 metaDataList << data; 0302 } else { 0303 std::cerr << i18n("Could not find module '%1'. See kcmshell5 --list for the full list of modules.", arg).toLocal8Bit().constData() << std::endl; 0304 } 0305 #endif 0306 } 0307 } 0308 0309 /* Check if this particular module combination is already running */ 0310 app.setServiceName(serviceName); 0311 if (app.isRunning()) { 0312 app.waitForExit(); 0313 return 0; 0314 } 0315 0316 KPageDialog::FaceType ftype = KPageDialog::Plain; 0317 0318 const int modCount = metaDataList.count(); 0319 if (modCount == 0) { 0320 return -1; 0321 } 0322 0323 if (modCount > 1) { 0324 ftype = KPageDialog::List; 0325 } 0326 0327 KCMShellMultiDialog *dlg = new KCMShellMultiDialog(ftype); 0328 dlg->setAttribute(Qt::WA_DeleteOnClose); 0329 0330 if (parser.isSet(QStringLiteral("caption"))) { 0331 dlg->setWindowTitle(parser.value(QStringLiteral("caption"))); 0332 } else if (modCount == 1) { 0333 dlg->setWindowTitle(metaDataList.constFirst().name()); 0334 } 0335 0336 const QStringList moduleArgs = parser.value(QStringLiteral("args")).split(QRegularExpression(QStringLiteral(" +"))); 0337 if (metaDataList.size() == 1) { 0338 KPageWidgetItem *item = dlg->addModule(*metaDataList.cbegin(), moduleArgs); 0339 // This makes sure the content area is focused by default 0340 item->widget()->setFocus(Qt::MouseFocusReason); 0341 } else { 0342 for (const KPluginMetaData &m : std::as_const(metaDataList)) { 0343 dlg->addModule(m, moduleArgs); 0344 } 0345 } 0346 0347 if (parser.isSet(QStringLiteral("icon"))) { 0348 dlg->setWindowIcon(QIcon::fromTheme(parser.value(QStringLiteral("icon")))); 0349 } else { 0350 dlg->setWindowIcon(QIcon::fromTheme(metaDataList.constFirst().iconName())); 0351 } 0352 0353 if (app.desktopFileName() == QLatin1String("org.kde.kcmshell5")) { 0354 const QString path = metaDataList.constFirst().metaDataFileName(); 0355 0356 if (path.endsWith(QLatin1String(".desktop"))) { 0357 app.setDesktopFileName(path); 0358 } else { 0359 app.setDesktopFileName(metaDataList.constFirst().pluginId()); 0360 } 0361 } 0362 0363 dlg->show(); 0364 0365 app.exec(); 0366 0367 return 0; 0368 } 0369 // vim: sw=4 et sts=4