Changeset View
Changeset View
Standalone View
Standalone View
kcms/kded/kcmkded.cpp
1 | // vim: noexpandtab shiftwidth=4 tabstop=4 | 1 | // vim: noexpandtab shiftwidth=4 tabstop=4 | ||
---|---|---|---|---|---|
2 | /* This file is part of the KDE project | 2 | /* This file is part of the KDE project | ||
3 | Copyright (C) 2002 Daniel Molkentin <molkentin@kde.org> | 3 | Copyright (C) 2002 Daniel Molkentin <molkentin@kde.org> | ||
4 | Copyright (C) 2020 Kai Uwe Broulik <kde@broulik.de> | ||||
4 | 5 | | |||
5 | This program is free software; you can redistribute it and/or | 6 | This program is free software; you can redistribute it and/or | ||
6 | modify it under the terms of the GNU General Public | 7 | modify it under the terms of the GNU General Public | ||
7 | License as published by the Free Software Foundation; either | 8 | License as published by the Free Software Foundation; either | ||
8 | version 2 of the License, or (at your option) any later version. | 9 | version 2 of the License, or (at your option) any later version. | ||
9 | 10 | | |||
10 | This program is distributed in the hope that it will be useful, | 11 | This program is distributed in the hope that it will be useful, | ||
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of | 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
13 | General Public License for more details. | 14 | General Public License for more details. | ||
14 | 15 | | |||
15 | You should have received a copy of the GNU General Public License | 16 | You should have received a copy of the GNU General Public License | ||
16 | along with this program; see the file COPYING. If not, write to | 17 | along with this program; see the file COPYING. If not, write to | ||
17 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | 18 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||
18 | Boston, MA 02110-1301, USA. | 19 | Boston, MA 02110-1301, USA. | ||
19 | */ | 20 | */ | ||
20 | 21 | | |||
21 | #include "kcmkded.h" | 22 | #include "kcmkded.h" | ||
22 | 23 | | |||
23 | #include <QDBusInterface> | 24 | #include "debug.h" | ||
24 | #include <QDBusReply> | | |||
25 | #include <QGroupBox> | | |||
26 | #include <QPushButton> | | |||
27 | #include <QTimer> | | |||
28 | #include <QTreeWidget> | | |||
29 | #include <QVBoxLayout> | | |||
30 | #include <QHeaderView> | | |||
31 | #include <QDialogButtonBox> | | |||
32 | #include <QLoggingCategory> | | |||
33 | #include <QDialog> | | |||
34 | | ||||
35 | #include <kaboutdata.h> | | |||
36 | #include <kdesktopfile.h> | | |||
37 | #include <kmessagebox.h> | | |||
38 | #include <kservice.h> | | |||
39 | #include <kservicetypetrader.h> | | |||
40 | 25 | | |||
26 | #include <QDBusConnection> | ||||
27 | #include <QDBusConnectionInterface> | ||||
28 | #include <QDBusPendingCall> | ||||
29 | #include <QDBusPendingCallWatcher> | ||||
30 | | ||||
31 | #include <KAboutData> | ||||
32 | #include <KConfig> | ||||
41 | #include <KConfigGroup> | 33 | #include <KConfigGroup> | ||
42 | #include <KPluginInfo> | 34 | #include <KPluginInfo> | ||
43 | #include <KPluginFactory> | 35 | #include <KPluginFactory> | ||
44 | #include <KPluginLoader> | 36 | #include <KPluginLoader> | ||
45 | #include <KPluginMetaData> | 37 | #include <KPluginMetaData> | ||
46 | #include <KLocalizedString> | 38 | #include <KLocalizedString> | ||
47 | 39 | | |||
48 | K_PLUGIN_FACTORY(KDEDFactory, | 40 | #include <algorithm> | ||
49 | registerPlugin<KDEDConfig>(); | | |||
50 | ) | | |||
51 | K_EXPORT_PLUGIN(KDEDFactory("kcmkded")) | | |||
52 | | ||||
53 | Q_LOGGING_CATEGORY(KCM_KDED, "kcm_kded") | | |||
54 | | ||||
55 | 41 | | |||
56 | enum OnDemandColumns | 42 | #include "modulesmodel.h" | ||
57 | { | 43 | #include "filterproxymodel.h" | ||
58 | OnDemandService = 0, | | |||
59 | OnDemandStatus = 1, | | |||
60 | OnDemandDescription = 2 | | |||
61 | }; | | |||
62 | | ||||
63 | enum StartupColumns | | |||
64 | { | | |||
65 | StartupUse = 0, | | |||
66 | StartupService = 1, | | |||
67 | StartupStatus = 2, | | |||
68 | StartupDescription = 3 | | |||
69 | }; | | |||
70 | 44 | | |||
45 | #include "kded_interface.h" | ||||
71 | 46 | | |||
47 | K_PLUGIN_FACTORY_WITH_JSON(KCMStyleFactory, "kcmkded.json", registerPlugin<KDEDConfig>();) | ||||
72 | 48 | | |||
73 | static const int LibraryRole = Qt::UserRole + 1; | 49 | static const QString s_kdedServiceName = QStringLiteral("org.kde.kded5"); | ||
74 | 50 | | |||
75 | KDEDConfig::KDEDConfig(QWidget* parent, const QVariantList &) : | 51 | KDEDConfig::KDEDConfig(QObject* parent, const QVariantList &args) | ||
76 | KCModule( parent ) | 52 | : KQuickAddons::ConfigModule(parent, args) | ||
77 | { | 53 | , m_model(new ModulesModel(this)) | ||
78 | KAboutData *about = | 54 | , m_filteredModel(new FilterProxyModel(this)) | ||
79 | new KAboutData( I18N_NOOP( "kcmkded" ), i18n( "KDE Service Manager" ), | 55 | , m_kdedInterface(new org::kde::kded5(s_kdedServiceName, | ||
80 | QStringLiteral("1.0"), QString(), KAboutLicense::GPL, | 56 | QStringLiteral("/kded"), | ||
81 | i18n( "(c) 2002 Daniel Molkentin" ) ); | 57 | QDBusConnection::sessionBus())) | ||
58 | , m_kdedWatcher(new QDBusServiceWatcher(s_kdedServiceName, | ||||
59 | QDBusConnection::sessionBus(), | ||||
60 | QDBusServiceWatcher::WatchForOwnerChange, this)) | ||||
61 | { | ||||
62 | qmlRegisterUncreatableType<KDEDConfig>("org.kde.private.kcms.style", 1, 0, "KCM", QStringLiteral("Cannot create instances of KCM")); | ||||
63 | // FIXME Qt 5.14 qmlRegisterAnonymousType | ||||
64 | qmlRegisterType<ModulesModel>(); | ||||
65 | qmlRegisterType<FilterProxyModel>(); | ||||
66 | | ||||
67 | KAboutData *about = new KAboutData(QStringLiteral("kcm5_kded"), i18n("Background Services"), | ||||
68 | QStringLiteral("2.0"), QString(), KAboutLicense::GPL, | ||||
69 | i18n("(c) 2002 Daniel Molkentin, (c) 2020 Kai Uwe Broulik") | ||||
70 | ); | ||||
82 | about->addAuthor(i18n("Daniel Molkentin"), QString(),QStringLiteral("molkentin@kde.org")); | 71 | about->addAuthor(i18n("Daniel Molkentin"), QString(),QStringLiteral("molkentin@kde.org")); | ||
72 | about->addAuthor(i18n("Kai Uwe Broulik"), QString(),QStringLiteral("kde@broulik.de")); | ||||
83 | setAboutData( about ); | 73 | setAboutData(about); | ||
84 | setButtons(Apply|Default|Help); | 74 | setButtons(Apply|Default|Help); | ||
85 | setQuickHelp( i18n("<h1>Service Manager</h1><p>This module allows you to have an overview of all plugins of the " | | |||
86 | "KDE Daemon, also referred to as KDE Services. Generally, there are two types of service:</p>" | | |||
87 | "<ul><li>Services invoked at startup</li><li>Services called on demand</li></ul>" | | |||
88 | "<p>The latter are only listed for convenience. The startup services can be started and stopped. " | | |||
89 | "In Administrator mode, you can also define whether services should be loaded at startup.</p>" | | |||
90 | "<p><b> Use this with care: some services are vital for Plasma; do not deactivate services if you" | | |||
91 | " do not know what you are doing.</b></p>")); | | |||
92 | | ||||
93 | RUNNING = i18n("Running")+' '; | | |||
94 | NOT_RUNNING = i18n("Not running")+' '; | | |||
95 | | ||||
96 | QVBoxLayout *lay = new QVBoxLayout( this ); | | |||
97 | lay->setContentsMargins( 0, 0, 0, 0 ); | | |||
98 | | ||||
99 | QGroupBox *gb = new QGroupBox( i18n("Load-on-Demand Services"), this ); | | |||
100 | gb->setWhatsThis( i18n("This is a list of available KDE services which will " | | |||
101 | "be started on demand. They are only listed for convenience, as you " | | |||
102 | "cannot manipulate these services.")); | | |||
103 | lay->addWidget( gb ); | | |||
104 | | ||||
105 | QVBoxLayout *gblay = new QVBoxLayout( gb ); | | |||
106 | | ||||
107 | _lvLoD = new QTreeWidget( gb ); | | |||
108 | QStringList cols; | | |||
109 | cols.append( i18n("Service") ); | | |||
110 | cols.append( i18n("Status") ); | | |||
111 | cols.append( i18n("Description") ); | | |||
112 | _lvLoD->setHeaderLabels( cols ); | | |||
113 | _lvLoD->setAllColumnsShowFocus(true); | | |||
114 | _lvLoD->setRootIsDecorated( false ); | | |||
115 | _lvLoD->setSortingEnabled(true); | | |||
116 | _lvLoD->sortByColumn(OnDemandService, Qt::AscendingOrder); | | |||
117 | _lvLoD->header()->setStretchLastSection(true); | | |||
118 | gblay->addWidget( _lvLoD ); | | |||
119 | | ||||
120 | gb = new QGroupBox( i18n("Startup Services"), this ); | | |||
121 | gb->setWhatsThis( i18n("This shows all KDE services that can be loaded " | | |||
122 | "on Plasma startup. Checked services will be invoked on next startup. " | | |||
123 | "Be careful with deactivation of unknown services.")); | | |||
124 | lay->addWidget( gb ); | | |||
125 | | ||||
126 | gblay = new QVBoxLayout( gb ); | | |||
127 | | ||||
128 | _lvStartup = new QTreeWidget( gb ); | | |||
129 | cols.clear(); | | |||
130 | cols.append( i18n("Use") ); | | |||
131 | cols.append( i18n("Service") ); | | |||
132 | cols.append( i18n("Status") ); | | |||
133 | cols.append( i18n("Description") ); | | |||
134 | _lvStartup->setHeaderLabels( cols ); | | |||
135 | _lvStartup->setAllColumnsShowFocus(true); | | |||
136 | _lvStartup->setRootIsDecorated( false ); | | |||
137 | _lvStartup->setSortingEnabled(true); | | |||
138 | _lvStartup->sortByColumn(StartupService, Qt::AscendingOrder); | | |||
139 | _lvStartup->header()->setStretchLastSection(true); | | |||
140 | gblay->addWidget( _lvStartup ); | | |||
141 | | ||||
142 | QDialogButtonBox *buttonBox = new QDialogButtonBox(Qt::Horizontal, gb); | | |||
143 | _pbStart = buttonBox->addButton( i18n("Start") , QDialogButtonBox::ActionRole ); | | |||
144 | _pbStop = buttonBox->addButton( i18n("Stop") , QDialogButtonBox::ActionRole ); | | |||
145 | gblay->addWidget( buttonBox ); | | |||
146 | | ||||
147 | _pbStart->setEnabled( false ); | | |||
148 | _pbStop->setEnabled( false ); | | |||
149 | | ||||
150 | connect(_pbStart, &QPushButton::clicked, this, &KDEDConfig::slotStartService); | | |||
151 | connect(_pbStop, &QPushButton::clicked, this, &KDEDConfig::slotStopService); | | |||
152 | connect(_lvLoD, &QTreeWidget::itemSelectionChanged, this, &KDEDConfig::slotLodItemSelected); | | |||
153 | connect(_lvStartup, &QTreeWidget::itemSelectionChanged, this, &KDEDConfig::slotStartupItemSelected); | | |||
154 | connect(_lvStartup, &QTreeWidget::itemChanged, this, &KDEDConfig::slotItemChecked); | | |||
155 | | ||||
156 | } | | |||
157 | | ||||
158 | QString setModuleGroup(const KPluginMetaData &module) | | |||
159 | { | | |||
160 | return QStringLiteral("Module-%1").arg(module.pluginId()); | | |||
161 | } | | |||
162 | | ||||
163 | bool KDEDConfig::autoloadEnabled(KConfig *config, const KPluginMetaData &module) | | |||
164 | { | | |||
165 | KConfigGroup cg(config, setModuleGroup(module)); | | |||
166 | return cg.readEntry("autoload", true); | | |||
167 | } | | |||
168 | | ||||
169 | void KDEDConfig::setAutoloadEnabled(KConfig *config, const KPluginMetaData &module, bool b) | | |||
170 | { | | |||
171 | KConfigGroup cg(config, setModuleGroup(module)); | | |||
172 | return cg.writeEntry("autoload", b); | | |||
173 | } | | |||
174 | | ||||
175 | // This code was copied from kded.cpp | | |||
176 | // TODO: move this KCM to the KDED framework and share the code? | | |||
177 | static QVector<KPluginMetaData> availableModules() | | |||
178 | { | | |||
179 | QVector<KPluginMetaData> plugins = KPluginLoader::findPlugins(QStringLiteral("kf5/kded")); | | |||
180 | QSet<QString> moduleIds; | | |||
181 | foreach (const KPluginMetaData &md, plugins) { | | |||
182 | moduleIds.insert(md.pluginId()); | | |||
183 | } | | |||
184 | // also search for old .desktop based kded modules | | |||
185 | KPluginInfo::List oldStylePlugins = KPluginInfo::fromServices(KServiceTypeTrader::self()->query(QStringLiteral("KDEDModule"))); | | |||
186 | foreach (const KPluginInfo &info, oldStylePlugins) { | | |||
187 | if (moduleIds.contains(info.pluginName())) { | | |||
188 | qCWarning(KCM_KDED).nospace() << "kded module " << info.pluginName() << " has already been found using " | | |||
189 | "JSON metadata, please don't install the now unneeded .desktop file (" << info.entryPath() << ")."; | | |||
190 | } else { | | |||
191 | qCDebug(KCM_KDED).nospace() << "kded module " << info.pluginName() << " still uses .desktop files (" | | |||
192 | << info.entryPath() << "). Please port it to JSON metadata."; | | |||
193 | plugins.append(info.toMetaData()); | | |||
194 | } | | |||
195 | } | | |||
196 | return plugins; | | |||
197 | } | | |||
198 | 75 | | |||
199 | // this code was copied from kded.cpp | 76 | m_filteredModel->setSourceModel(m_model); | ||
200 | static bool isModuleLoadedOnDemand(const KPluginMetaData &module) | | |||
201 | { | | |||
202 | bool loadOnDemand = true; | | |||
203 | // use toVariant() since it could be string or bool in the json and QJsonObject does not convert | | |||
204 | QVariant p = module.rawData().value(QStringLiteral("X-KDE-Kded-load-on-demand")).toVariant(); | | |||
205 | if (p.isValid() && p.canConvert<bool>() && (p.toBool() == false)) { | | |||
206 | loadOnDemand = false; | | |||
207 | } | | |||
208 | return loadOnDemand; | | |||
209 | } | | |||
210 | 77 | | |||
211 | void KDEDConfig::load() | 78 | connect(m_model, &ModulesModel::autoloadedModulesChanged, this, [this] { | ||
212 | { | 79 | setNeedsSave(true); | ||
213 | KConfig kdedrc(QStringLiteral("kded5rc"), KConfig::NoGlobals); | 80 | }); | ||
214 | 81 | | |||
215 | _lvStartup->clear(); | 82 | connect(m_kdedWatcher, &QDBusServiceWatcher::serviceOwnerChanged, this, | ||
216 | _lvLoD->clear(); | 83 | [this](const QString &service, const QString &oldOwner, const QString &newOwner) { | ||
84 | Q_UNUSED(service) | ||||
85 | Q_UNUSED(oldOwner) | ||||
217 | 86 | | |||
218 | QTreeWidgetItem* treeitem = nullptr; | 87 | const bool running = !newOwner.isEmpty(); | ||
219 | const auto modules = availableModules(); | 88 | if (m_kdedRunning != running) { | ||
220 | for (const KPluginMetaData &mod : modules) { | 89 | m_kdedRunning = running; | ||
221 | QString servicePath = mod.metaDataFileName(); | 90 | emit kdedRunningChanged(); | ||
222 | | ||||
223 | // autoload defaults to false if it is not found | | |||
224 | const bool autoload = mod.rawData().value(QStringLiteral("X-KDE-Kded-autoload")).toVariant().toBool(); | | |||
225 | // keep estimating dbusModuleName in sync with KDEDModule (kdbusaddons) and kded (kded) | | |||
226 | // currently (KF5) the module name in the D-Bus object path is set by the pluginId | | |||
227 | const QString dbusModuleName = mod.pluginId(); | | |||
228 | qCDebug(KCM_KDED) << "reading kded info from" << servicePath << "autoload =" << autoload << "dbus module name =" << dbusModuleName; | | |||
229 | | ||||
230 | // The logic has to be identical to Kded::initModules. | | |||
231 | // They interpret X-KDE-Kded-autoload as false if not specified | | |||
232 | // X-KDE-Kded-load-on-demand as true if not specified | | |||
233 | if (autoload) { | | |||
234 | treeitem = new QTreeWidgetItem(); | | |||
235 | treeitem->setCheckState(StartupUse, autoloadEnabled(&kdedrc, mod) ? Qt::Checked : Qt::Unchecked); | | |||
236 | treeitem->setText(StartupService, mod.name()); | | |||
237 | treeitem->setText(StartupDescription, mod.description()); | | |||
238 | treeitem->setText(StartupStatus, NOT_RUNNING); | | |||
239 | treeitem->setData(StartupService, LibraryRole, dbusModuleName); | | |||
240 | _lvStartup->addTopLevelItem(treeitem); | | |||
241 | } | | |||
242 | else if (isModuleLoadedOnDemand(mod)) { | | |||
243 | treeitem = new QTreeWidgetItem(); | | |||
244 | treeitem->setText(OnDemandService, mod.name() ); | | |||
245 | treeitem->setText(OnDemandDescription, mod.description()); | | |||
246 | treeitem->setText(OnDemandStatus, NOT_RUNNING); | | |||
247 | treeitem->setData(OnDemandService, LibraryRole, dbusModuleName); | | |||
248 | _lvLoD->addTopLevelItem(treeitem); | | |||
249 | } | | |||
250 | else { | | |||
251 | qCWarning(KCM_KDED) << "kcmkded: Module " << mod.name() << "from file" << mod.metaDataFileName() << " not loaded on demand or startup! Skipping."; | | |||
252 | } | 91 | } | ||
92 | }); | ||||
93 | m_kdedRunning = QDBusConnection::sessionBus().interface()->isServiceRegistered(s_kdedServiceName); | ||||
253 | } | 94 | } | ||
254 | 95 | | |||
255 | _lvStartup->resizeColumnToContents(StartupUse); | 96 | ModulesModel *KDEDConfig::model() const | ||
256 | _lvStartup->resizeColumnToContents(StartupService); | 97 | { | ||
257 | _lvStartup->resizeColumnToContents(StartupStatus); | 98 | return m_model; | ||
258 | | ||||
259 | _lvLoD->resizeColumnToContents(OnDemandService); | | |||
260 | _lvLoD->resizeColumnToContents(OnDemandStatus); | | |||
261 | | ||||
262 | getServiceStatus(); | | |||
263 | | ||||
264 | emit changed(false); | | |||
265 | } | 99 | } | ||
266 | 100 | | |||
267 | void KDEDConfig::save() | 101 | FilterProxyModel *KDEDConfig::filteredModel() const | ||
268 | { | 102 | { | ||
269 | KConfig kdedrc(QStringLiteral("kded5rc"), KConfig::NoGlobals); | 103 | return m_filteredModel; | ||
270 | | ||||
271 | const auto modules = availableModules(); | | |||
272 | for (const KPluginMetaData &mod : modules) { | | |||
273 | qCDebug(KCM_KDED) << "saving settings for kded module" << mod.pluginId(); | | |||
274 | // autoload defaults to false if it is not found | | |||
275 | const bool autoload = mod.rawData().value(QStringLiteral("X-KDE-Kded-autoload")).toVariant().toBool(); | | |||
276 | if (autoload) { | | |||
277 | const QString libraryName = mod.pluginId(); | | |||
278 | int count = _lvStartup->topLevelItemCount(); | | |||
279 | for(int i = 0; i < count; ++i) { | | |||
280 | QTreeWidgetItem *treeitem = _lvStartup->topLevelItem(i); | | |||
281 | if ( treeitem->data(StartupService, LibraryRole ).toString() == libraryName) { | | |||
282 | // we found a match, now compare and see what changed | | |||
283 | setAutoloadEnabled(&kdedrc, mod, treeitem->checkState( StartupUse ) == Qt::Checked); | | |||
284 | break; | | |||
285 | } | | |||
286 | } | | |||
287 | } | | |||
288 | } | 104 | } | ||
289 | kdedrc.sync(); | | |||
290 | | ||||
291 | emit changed(false); | | |||
292 | 105 | | |||
293 | QDBusInterface kdedInterface(QStringLiteral("org.kde.kded5"), QStringLiteral("/kded"), QStringLiteral("org.kde.kded5")); | 106 | bool KDEDConfig::kdedRunning() const | ||
294 | kdedInterface.call(QStringLiteral("reconfigure")); | 107 | { | ||
295 | QTimer::singleShot(0, this, &KDEDConfig::slotServiceRunningToggled); | 108 | return m_kdedRunning; | ||
296 | } | 109 | } | ||
297 | 110 | | |||
298 | 111 | void KDEDConfig::startModule(const QString &moduleName) | |||
299 | void KDEDConfig::defaults() | | |||
300 | { | 112 | { | ||
301 | int count = _lvStartup->topLevelItemCount(); | 113 | startOrStopModule(moduleName, Running); | ||
302 | for( int i = 0; i < count; ++i ) | | |||
303 | { | | |||
304 | _lvStartup->topLevelItem( i )->setCheckState( StartupUse, Qt::Checked ); | | |||
305 | } | 114 | } | ||
306 | 115 | | |||
307 | getServiceStatus(); | 116 | void KDEDConfig::stopModule(const QString &moduleName) | ||
308 | 117 | { | |||
309 | emit changed(true); | 118 | startOrStopModule(moduleName, NotRunning); | ||
310 | } | 119 | } | ||
311 | 120 | | |||
312 | 121 | void KDEDConfig::startOrStopModule(const QString &moduleName, ModuleStatus status) | |||
313 | void KDEDConfig::getServiceStatus() | | |||
314 | { | 122 | { | ||
315 | QStringList modules; | 123 | auto call = (status == NotRunning ? m_kdedInterface->unloadModule(moduleName) | ||
316 | QDBusInterface kdedInterface( QStringLiteral("org.kde.kded5"), QStringLiteral("/kded"), QStringLiteral("org.kde.kded5") ); | 124 | : m_kdedInterface->loadModule(moduleName)); | ||
317 | QDBusReply<QStringList> reply = kdedInterface.call( QStringLiteral("loadedModules") ); | 125 | | ||
126 | QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(call, this); | ||||
127 | connect(callWatcher, &QDBusPendingCallWatcher::finished, this, [this, moduleName, status](QDBusPendingCallWatcher *watcher) { | ||||
128 | QDBusPendingReply<bool> reply = *watcher; | ||||
129 | watcher->deleteLater(); | ||||
318 | 130 | | |||
319 | if ( reply.isValid() ) { | 131 | if (reply.isError()) { | ||
320 | modules = reply.value(); | 132 | if (status == NotRunning) { | ||
133 | emit errorMessage(i18n("Failed to stop service: %1", reply.error().message())); | ||||
134 | } else { | ||||
135 | emit errorMessage(i18n("Failed to start service: %1", reply.error().message())); | ||||
321 | } | 136 | } | ||
322 | else { | | |||
323 | _lvLoD->setEnabled( false ); | | |||
324 | _lvStartup->setEnabled( false ); | | |||
325 | KMessageBox::error(this, i18n("Unable to contact KDED.")); | | |||
326 | return; | 137 | return; | ||
327 | } | 138 | } | ||
328 | qCDebug(KCM_KDED) << "Loaded kded modules:" << modules; | | |||
329 | 139 | | |||
330 | // Initialize | 140 | if (!reply.value()) { | ||
331 | int count = _lvLoD->topLevelItemCount(); | 141 | if (status == NotRunning) { | ||
332 | for( int i = 0; i < count; ++i ) | 142 | emit errorMessage(i18n("Failed to stop service.")); | ||
333 | _lvLoD->topLevelItem( i )->setText( OnDemandStatus, NOT_RUNNING ); | 143 | } else { | ||
334 | count = _lvStartup->topLevelItemCount(); | 144 | emit errorMessage(i18n("Failed to start service.")); | ||
335 | for( int i = 0; i < count; ++i ) | | |||
336 | _lvStartup->topLevelItem( i )->setText( StartupStatus, NOT_RUNNING ); | | |||
337 | | ||||
338 | // Fill | | |||
339 | foreach( const QString& module, modules ) | | |||
340 | { | | |||
341 | bool found = false; | | |||
342 | | ||||
343 | count = _lvLoD->topLevelItemCount(); | | |||
344 | for( int i = 0; i < count; ++i ) | | |||
345 | { | | |||
346 | QTreeWidgetItem *treeitem = _lvLoD->topLevelItem( i ); | | |||
347 | if ( treeitem->data( OnDemandService, LibraryRole ).toString() == module ) | | |||
348 | { | | |||
349 | treeitem->setText( OnDemandStatus, RUNNING ); | | |||
350 | found = true; | | |||
351 | break; | | |||
352 | } | 145 | } | ||
146 | return; | ||||
353 | } | 147 | } | ||
354 | 148 | | |||
355 | count = _lvStartup->topLevelItemCount(); | 149 | qCDebug(KCM_KDED) << "Successfully" << (status == Running ? "started" : "stopped") << moduleName; | ||
356 | for( int i = 0; i < count; ++i ) | 150 | if (status == Running) { | ||
357 | { | 151 | m_lastStartedModule = moduleName; | ||
358 | QTreeWidgetItem *treeitem = _lvStartup->topLevelItem( i ); | 152 | } else { | ||
359 | if ( treeitem->data( StartupService, LibraryRole ).toString() == module ) | 153 | m_lastStartedModule.clear(); | ||
360 | { | | |||
361 | treeitem->setText( StartupStatus, RUNNING ); | | |||
362 | found = true; | | |||
363 | break; | | |||
364 | } | 154 | } | ||
155 | getModuleStatus(); | ||||
156 | }); | ||||
365 | } | 157 | } | ||
366 | 158 | | |||
367 | if (!found) | 159 | void KDEDConfig::getModuleStatus() | ||
368 | { | | |||
369 | qCDebug(KCM_KDED) << "Could not relate module " << module; | | |||
370 | #ifndef NDEBUG | | |||
371 | qCDebug(KCM_KDED) << "Candidates were:"; | | |||
372 | count = _lvLoD->topLevelItemCount(); | | |||
373 | for( int i = 0; i < count; ++i ) | | |||
374 | { | 160 | { | ||
375 | QTreeWidgetItem *treeitem = _lvLoD->topLevelItem( i ); | 161 | auto call = m_kdedInterface->loadedModules(); | ||
376 | qCDebug(KCM_KDED) << treeitem->data( OnDemandService, LibraryRole ).toString(); | | |||
377 | } | | |||
378 | 162 | | |||
379 | count = _lvStartup->topLevelItemCount(); | 163 | QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(call, this); | ||
380 | for( int i = 0; i < count; ++i ) | 164 | connect(callWatcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) { | ||
381 | { | 165 | QDBusPendingReply<QStringList> reply = *watcher; | ||
382 | QTreeWidgetItem *treeitem = _lvStartup->topLevelItem( i ); | 166 | watcher->deleteLater(); | ||
383 | qCDebug(KCM_KDED) << treeitem->data( StartupService, LibraryRole ).toString(); | | |||
384 | } | | |||
385 | #endif | | |||
386 | } | | |||
387 | 167 | | |||
168 | if (reply.isError()) { | ||||
169 | qCWarning(KCM_KDED) << "Failed to get loaded modules" << reply.error().name() << reply.error().message(); | ||||
170 | return; | ||||
388 | } | 171 | } | ||
389 | 172 | | |||
390 | } | 173 | QStringList runningModules = reply.value(); | ||
174 | m_model->setRunningModules(runningModules); | ||||
391 | 175 | | |||
392 | void KDEDConfig::slotReload() | 176 | // Check if the user just tried starting a module that then disabled itself again. | ||
393 | { | 177 | // Some kded modules disable themselves on start when they deem themselves unnecessary | ||
394 | QString current; | 178 | // based on some configuration independ of kded or the current environment. | ||
395 | if ( !_lvStartup->selectedItems().isEmpty() ) | 179 | // At least provide some feedback and not leave the user wondering why the service doesn't start. | ||
396 | current = _lvStartup->selectedItems().at(0)->data( StartupService, LibraryRole ).toString(); | 180 | if (!m_lastStartedModule.isEmpty() && !runningModules.contains(m_lastStartedModule)) { | ||
397 | load(); | 181 | emit showSelfDisablingModulesHint(); | ||
398 | if ( !current.isEmpty() ) | | |||
399 | { | | |||
400 | int count = _lvStartup->topLevelItemCount(); | | |||
401 | for( int i = 0; i < count; ++i ) | | |||
402 | { | | |||
403 | QTreeWidgetItem *treeitem = _lvStartup->topLevelItem( i ); | | |||
404 | if ( treeitem->data( StartupService, LibraryRole ).toString() == current ) | | |||
405 | { | | |||
406 | _lvStartup->setCurrentItem( treeitem, 0, QItemSelectionModel::ClearAndSelect ); | | |||
407 | break; | | |||
408 | } | | |||
409 | } | | |||
410 | } | 182 | } | ||
183 | m_lastStartedModule.clear(); | ||||
184 | }); | ||||
411 | } | 185 | } | ||
412 | 186 | | |||
413 | void KDEDConfig::slotStartupItemSelected() | 187 | void KDEDConfig::load() | ||
414 | { | 188 | { | ||
415 | if ( _lvStartup->selectedItems().isEmpty() ) { | 189 | m_model->load(); | ||
416 | // Disable the buttons | | |||
417 | _pbStart->setEnabled( false ); | | |||
418 | _pbStop->setEnabled( false ); | | |||
419 | return; | | |||
420 | } | | |||
421 | 190 | | |||
422 | // Deselect a currently selected element in the "load on demand" treeview | 191 | getModuleStatus(); | ||
423 | _lvLoD->setCurrentItem(nullptr, 0, QItemSelectionModel::Clear); | | |||
424 | 192 | | |||
425 | QTreeWidgetItem *item = _lvStartup->selectedItems().at(0); | 193 | setNeedsSave(false); | ||
426 | if ( item->text(StartupStatus) == RUNNING ) { | | |||
427 | _pbStart->setEnabled( false ); | | |||
428 | _pbStop->setEnabled( true ); | | |||
429 | } | | |||
430 | else if ( item->text(StartupStatus) == NOT_RUNNING ) { | | |||
431 | _pbStart->setEnabled( true ); | | |||
432 | _pbStop->setEnabled( false ); | | |||
433 | } | | |||
434 | else // Error handling, better do nothing | | |||
435 | { | | |||
436 | _pbStart->setEnabled( false ); | | |||
437 | _pbStop->setEnabled( false ); | | |||
438 | } | | |||
439 | | ||||
440 | getServiceStatus(); | | |||
441 | } | 194 | } | ||
442 | 195 | | |||
443 | void KDEDConfig::slotLodItemSelected() | 196 | void KDEDConfig::save() | ||
444 | { | 197 | { | ||
445 | if ( _lvLoD->selectedItems().isEmpty() ) | 198 | KConfig kdedrc(QStringLiteral("kded5rc"), KConfig::NoGlobals); | ||
446 | return; | | |||
447 | 199 | | |||
448 | // Deselect a currently selected element in the "load on startup" treeview | 200 | for (int i = 0; i < m_model->rowCount(); ++i) { | ||
449 | _lvStartup->setCurrentItem(nullptr, 0, QItemSelectionModel::Clear); | 201 | const QModelIndex idx = m_model->index(i, 0); | ||
450 | } | | |||
451 | 202 | | |||
452 | void KDEDConfig::slotServiceRunningToggled() | 203 | const auto type = static_cast<ModuleType>(idx.data(ModulesModel::TypeRole).toInt()); | ||
453 | { | 204 | if (type != AutostartType) { | ||
454 | getServiceStatus(); | 205 | continue; | ||
455 | slotStartupItemSelected(); | | |||
456 | } | 206 | } | ||
457 | 207 | | |||
458 | void KDEDConfig::slotStartService() | 208 | const QString moduleName = idx.data(ModulesModel::ModuleNameRole).toString(); | ||
459 | { | | |||
460 | QString service = _lvStartup->selectedItems().at(0)->data( StartupService, LibraryRole ).toString(); | | |||
461 | | ||||
462 | QDBusInterface kdedInterface( QStringLiteral("org.kde.kded5"), QStringLiteral("/kded"),QStringLiteral("org.kde.kded5") ); | | |||
463 | QDBusReply<bool> reply = kdedInterface.call( QStringLiteral("loadModule"), service ); | | |||
464 | 209 | | |||
465 | if ( reply.isValid() ) { | 210 | const bool autoloadEnabled = idx.data(ModulesModel::AutoloadEnabledRole).toBool(); | ||
466 | if ( reply.value() ) | 211 | KConfigGroup cg(&kdedrc, QStringLiteral("Module-%1").arg(moduleName)); | ||
467 | slotServiceRunningToggled(); | 212 | cg.writeEntry("autoload", autoloadEnabled); | ||
468 | else | | |||
469 | KMessageBox::error(this, "<qt>" + i18n("Unable to start server <em>%1</em>.", service) + "</qt>"); | | |||
470 | } | | |||
471 | else { | | |||
472 | KMessageBox::error(this, "<qt>" + i18n("Unable to start service <em>%1</em>.<br /><br /><i>Error: %2</i>", | | |||
473 | service, reply.error().message()) + "</qt>" ); | | |||
474 | } | | |||
475 | } | 213 | } | ||
476 | 214 | | |||
477 | void KDEDConfig::slotStopService() | 215 | kdedrc.sync(); | ||
478 | { | | |||
479 | QString service = _lvStartup->selectedItems().at(0)->data( StartupService, LibraryRole ).toString(); | | |||
480 | qCDebug(KCM_KDED) << "Stopping: " << service; | | |||
481 | 216 | | |||
482 | QDBusInterface kdedInterface( QStringLiteral("org.kde.kded5"), QStringLiteral("/kded"), QStringLiteral("org.kde.kded5") ); | 217 | setNeedsSave(false); | ||
483 | QDBusReply<bool> reply = kdedInterface.call( QStringLiteral("unloadModule"), service ); | | |||
484 | 218 | | |||
485 | if ( reply.isValid() ) { | 219 | // Is all of this really necessary? I would also think it to be fire and forget... | ||
486 | if ( reply.value() ) | 220 | // Only if changing autoload for a module may load/unload it, otherwise there's no point. | ||
487 | slotServiceRunningToggled(); | 221 | // Autoload doesn't affect a running session and reloading the running modules is also useless then. | ||
488 | else | 222 | auto call = m_kdedInterface->reconfigure(); | ||
489 | KMessageBox::error(this, "<qt>" + i18n("Unable to stop service <em>%1</em>.", service) + "</qt>"); | 223 | QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(call, this); | ||
490 | } | 224 | connect(callWatcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) { | ||
491 | else { | 225 | QDBusPendingReply<void> reply = *watcher; | ||
492 | KMessageBox::error(this, "<qt>" + i18n("Unable to stop service <em>%1</em>.<br /><br /><i>Error: %2</i>", | 226 | watcher->deleteLater(); | ||
493 | service, reply.error().message()) + "</qt>" ); | 227 | | ||
228 | if (reply.isError()) { | ||||
229 | emit errorMessage(i18n("Failed to notify KDE Service Manager (kded5) of saved changed: %1", reply.error().message())); | ||||
230 | return; | ||||
494 | } | 231 | } | ||
232 | | ||||
233 | qCDebug(KCM_KDED) << "Successfully reconfigured kded"; | ||||
234 | getModuleStatus(); | ||||
235 | }); | ||||
495 | } | 236 | } | ||
496 | 237 | | |||
497 | void KDEDConfig::slotItemChecked(QTreeWidgetItem*, int column) | 238 | void KDEDConfig::defaults() | ||
498 | { | 239 | { | ||
499 | // We only listen to changes the user did. | 240 | for (int i = 0; i < m_model->rowCount(); ++i) { | ||
500 | if (column==StartupUse) { | 241 | const QModelIndex idx = m_model->index(i, 0); | ||
501 | emit changed(true); | 242 | if (m_model->setData(idx, true, ModulesModel::AutoloadEnabledRole)) { | ||
243 | setNeedsSave(true); | ||||
244 | } | ||||
502 | } | 245 | } | ||
503 | } | 246 | } | ||
504 | 247 | | |||
505 | #include "kcmkded.moc" | 248 | #include "kcmkded.moc" |