1/*
2 SPDX-FileCopyrightText: 1999 Matthias Hoelzer-Kluepfel <hoelzer@kde.org>
3 SPDX-FileCopyrightText: 2000 Matthias Elter <elter@kde.org>
4 SPDX-FileCopyrightText: 2004 Frans Englich <frans.englich@telia.com>
5 SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8
9*/
10
11#include <QApplication>
12#include <QCommandLineOption>
13#include <QCommandLineParser>
14#include <QDebug>
15#include <QIcon>
16#include <QQmlEngine>
17#include <QRegularExpression>
18#include <QStandardPaths>
19
20#include <KAboutData>
21#include <KAuthorized>
22#include <KCModule>
23#include <KCMultiDialog>
24#include <KLocalizedString>
25#include <KPageDialog>
26#include <KPluginMetaData>
27#include <KShell>
28#include <kcmutils_debug.h>
29
30#if HAVE_QTDBUS
31#include <QDBusConnection>
32#include <QDBusConnectionInterface>
33#endif
34
35#include <algorithm>
36#include <iostream>
37
38inline QList<KPluginMetaData> findKCMsMetaData()
39{
40 QList<KPluginMetaData> metaDataList = KPluginMetaData::findPlugins(QStringLiteral("plasma/kcms"));
41 metaDataList << KPluginMetaData::findPlugins(QStringLiteral("plasma/kcms/systemsettings"));
42 metaDataList << KPluginMetaData::findPlugins(QStringLiteral("plasma/kcms/systemsettings_qwidgets"));
43 metaDataList << KPluginMetaData::findPlugins(QStringLiteral("plasma/kcms/kinfocenter"));
44 return metaDataList;
45}
46
47class KCMShellMultiDialog : public KCMultiDialog
48{
49 Q_OBJECT
50
51public:
52 /*
53 * Constructor. Parameter dialogFace is passed to KCMultiDialog
54 * unchanged.
55 */
56 explicit KCMShellMultiDialog(KPageDialog::FaceType dialogFace)
57 : KCMultiDialog()
58 {
59 setFaceType(dialogFace);
60 setModal(false);
61
62#if HAVE_QTDBUS
63 connect(sender: this, signal: &KCMShellMultiDialog::currentPageChanged, context: this, slot: [](KPageWidgetItem *newPage) {
64 if (KCModule *activeModule = newPage->widget()->findChild<KCModule *>()) {
65 if (QDBusConnection::sessionBus().isConnected()
66 && QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.kde.ActivityManager"))) {
67 QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.ActivityManager"),
68 QStringLiteral("/ActivityManager/Resources"),
69 QStringLiteral("org.kde.ActivityManager.Resources"),
70 QStringLiteral("RegisterResourceEvent"));
71
72 const QString appId = QStringLiteral("org.kde.systemsettings");
73 const uint winId = 0;
74 const QString url = QLatin1String("kcm:") + activeModule->metaData().pluginId();
75 const uint eventType = 0; // Accessed
76
77 msg.setArguments({appId, winId, url, eventType});
78
79 QDBusConnection::sessionBus().asyncCall(message: msg);
80 }
81 }
82 });
83#endif
84 }
85};
86
87int main(int argc, char *argv[])
88{
89 const bool qpaVariable = qEnvironmentVariableIsSet(varName: "QT_QPA_PLATFORM");
90 QApplication app(argc, argv);
91 if (!qpaVariable) {
92 // don't leak the env variable to processes we start
93 qunsetenv(varName: "QT_QPA_PLATFORM");
94 }
95 KLocalizedString::setApplicationDomain("kcmshell6");
96 KAboutData aboutData(QStringLiteral("kcmshell6"),
97 QString(),
98 QLatin1String(PROJECT_VERSION),
99 i18n("A tool to start single system settings modules"),
100 KAboutLicense::GPL,
101 i18n("(c) 1999-2023, The KDE Developers"));
102
103 aboutData.addAuthor(i18n("Frans Englich"), i18n("Maintainer"), QStringLiteral("frans.englich@kde.org"));
104 aboutData.addAuthor(i18n("Daniel Molkentin"), task: QString(), QStringLiteral("molkentin@kde.org"));
105 aboutData.addAuthor(i18n("Matthias Hoelzer-Kluepfel"), task: QString(), QStringLiteral("hoelzer@kde.org"));
106 aboutData.addAuthor(i18n("Matthias Elter"), task: QString(), QStringLiteral("elter@kde.org"));
107 aboutData.addAuthor(i18n("Matthias Ettrich"), task: QString(), QStringLiteral("ettrich@kde.org"));
108 aboutData.addAuthor(i18n("Waldo Bastian"), task: QString(), QStringLiteral("bastian@kde.org"));
109 KAboutData::setApplicationData(aboutData);
110
111 QCommandLineParser parser;
112 aboutData.setupCommandLine(&parser);
113
114 parser.addOption(commandLineOption: QCommandLineOption(QStringLiteral("list"), i18n("List all possible modules")));
115 parser.addPositionalArgument(QStringLiteral("module"), i18n("Configuration module to open"));
116 parser.addOption(commandLineOption: QCommandLineOption(QStringLiteral("args"), i18n("Space separated arguments for the module"), QLatin1String("arguments")));
117 parser.addOption(commandLineOption: QCommandLineOption(QStringLiteral("icon"), i18n("Use a specific icon for the window"), QLatin1String("icon")));
118 parser.addOption(commandLineOption: QCommandLineOption(QStringLiteral("caption"), i18n("Use a specific caption for the window"), QLatin1String("caption")));
119 parser.addOption(commandLineOption: QCommandLineOption(QStringLiteral("highlight"), i18n("Show an indicator when settings have changed from their default value")));
120
121 parser.parse(arguments: app.arguments());
122 aboutData.processCommandLine(parser: &parser);
123
124 parser.process(app);
125
126 if (parser.isSet(QStringLiteral("list"))) {
127 std::cout << i18n("The following modules are available:").toLocal8Bit().constData() << '\n';
128
129 QList<KPluginMetaData> plugins = findKCMsMetaData();
130 int maxLen = 0;
131
132 for (const auto &plugin : plugins) {
133 const int len = plugin.pluginId().size();
134 maxLen = std::max(a: maxLen, b: len);
135 }
136
137 for (const auto &plugin : plugins) {
138 QString comment = plugin.description();
139 if (comment.isEmpty()) {
140 comment = i18n("No description available");
141 }
142
143 const QString entry = QStringLiteral("%1 - %2").arg(args: plugin.pluginId().leftJustified(width: maxLen, fill: QLatin1Char(' ')), args&: comment);
144
145 std::cout << entry.toLocal8Bit().constData() << '\n';
146 }
147
148 std::cout << std::endl;
149
150 return 0;
151 }
152
153 if (parser.positionalArguments().isEmpty()) {
154 parser.showHelp();
155 return -1;
156 }
157
158 QList<KPluginMetaData> metaDataList;
159
160 QStringList args = parser.positionalArguments();
161 args.removeDuplicates();
162 for (const QString &arg : args) {
163 if (KPluginMetaData data(arg, KPluginMetaData::AllowEmptyMetaData); data.isValid()) {
164 metaDataList << data;
165 } else {
166 // Look in the namespaces for systemsettings/kinfocenter
167 const static auto knownKCMs = findKCMsMetaData();
168 const QStringList possibleIds{arg, QStringLiteral("kcm_") + arg, QStringLiteral("kcm") + arg};
169 bool found = std::any_of(first: knownKCMs.begin(), last: knownKCMs.end(), pred: [&possibleIds, &metaDataList](const KPluginMetaData &data) {
170 bool idMatches = possibleIds.contains(str: data.pluginId());
171 if (idMatches) {
172 metaDataList << data;
173 }
174 return idMatches;
175 });
176 if (!found) {
177 metaDataList << KPluginMetaData(arg); // So that we show an error message in the dialog
178 qCWarning(KCMUTILS_LOG) << "Could not find KCM with given Id" << arg;
179 }
180 }
181 }
182 if (metaDataList.isEmpty()) {
183 return -1;
184 }
185
186 // This ensures if there are multiple QML-based kcms loaded, they use a shared engine instance
187 app.setProperty(name: "__qmlEngine", value: QVariant::fromValue(value: new QQmlEngine));
188 const bool multipleKCMs = metaDataList.size() > 1;
189 KPageDialog::FaceType ftype = multipleKCMs ? KPageDialog::List : KPageDialog::Plain;
190 auto dlg = new KCMShellMultiDialog(ftype);
191 dlg->setAttribute(Qt::WA_DeleteOnClose);
192
193 if (parser.isSet(QStringLiteral("caption"))) {
194 dlg->setWindowTitle(parser.value(QStringLiteral("caption")));
195 } else if (!multipleKCMs) { // We will have the "Configure" window title set by KCMultiDialog
196 dlg->setWindowTitle(metaDataList.constFirst().name());
197 }
198
199 const QStringList argSplit = KShell::splitArgs(cmd: parser.value(QStringLiteral("args")));
200 QVariantList pluginArgs(argSplit.begin(), argSplit.end());
201 if (metaDataList.size() == 1) {
202 KPageWidgetItem *item = dlg->addModule(metaData: *metaDataList.cbegin(), args: pluginArgs);
203 // This makes sure the content area is focused by default
204 item->widget()->setFocus(Qt::MouseFocusReason);
205 } else {
206 for (const KPluginMetaData &m : std::as_const(t&: metaDataList)) {
207 dlg->addModule(metaData: m, args: pluginArgs);
208 }
209 }
210
211 if (parser.isSet(QStringLiteral("icon"))) {
212 dlg->setWindowIcon(QIcon::fromTheme(name: parser.value(QStringLiteral("icon"))));
213 } else {
214 dlg->setWindowIcon(QIcon::fromTheme(name: metaDataList.constFirst().iconName()));
215 }
216
217 if (parser.isSet(QStringLiteral("highlight"))) {
218 dlg->setDefaultsIndicatorsVisible(true);
219 }
220
221 if (app.desktopFileName() == QLatin1String("org.kde.kcmshell6")) {
222 const QString path = metaDataList.constFirst().fileName();
223
224 if (path.endsWith(s: QLatin1String(".desktop"))) {
225 app.setDesktopFileName(path);
226 } else {
227 app.setDesktopFileName(metaDataList.constFirst().pluginId());
228 }
229 }
230
231 dlg->show();
232
233 app.exec();
234
235 return 0;
236}
237
238#include "main.moc"
239

source code of kcmutils/src/kcmshell/main.cpp