1 /*
2  *  SPDX-FileCopyrightText: 2014-2016 Sebastian Kügler <sebas@kde.org>
3  *
4  *  SPDX-License-Identifier: LGPL-2.1-or-later
5  */
6 
7 #include "doctor.h"
8 #include "dpmsclient.h"
9 
10 #include <QCollator>
11 #include <QCommandLineParser>
12 #include <QCoreApplication>
13 #include <QDateTime>
14 #include <QFile>
15 #include <QGuiApplication>
16 #include <QJsonArray>
17 #include <QJsonDocument>
18 #include <QJsonObject>
19 #include <QLoggingCategory>
20 #include <QRect>
21 #include <QStandardPaths>
22 
23 #include "../backendmanager_p.h"
24 #include "../config.h"
25 #include "../configoperation.h"
26 #include "../edid.h"
27 #include "../getconfigoperation.h"
28 #include "../log.h"
29 #include "../output.h"
30 #include "../setconfigoperation.h"
31 
32 Q_LOGGING_CATEGORY(KSCREEN_DOCTOR, "kscreen.doctor")
33 
34 static QTextStream cout(stdout);
35 static QTextStream cerr(stderr);
36 
37 const static QString green = QStringLiteral("\033[01;32m");
38 const static QString red = QStringLiteral("\033[01;31m");
39 const static QString yellow = QStringLiteral("\033[01;33m");
40 const static QString blue = QStringLiteral("\033[01;34m");
41 const static QString bold = QStringLiteral("\033[01;39m");
42 const static QString cr = QStringLiteral("\033[0;0m");
43 
44 namespace KScreen
45 {
46 namespace ConfigSerializer
47 {
48 // Exported private symbol in configserializer_p.h in KScreen
49 extern QJsonObject serializeConfig(const KScreen::ConfigPtr &config);
50 }
51 }
52 
53 using namespace KScreen;
54 
Doctor(QObject * parent)55 Doctor::Doctor(QObject *parent)
56     : QObject(parent)
57     , m_config(nullptr)
58     , m_changed(false)
59     , m_dpmsClient(nullptr)
60 {
61 }
62 
~Doctor()63 Doctor::~Doctor()
64 {
65 }
66 
start(QCommandLineParser * parser)67 void Doctor::start(QCommandLineParser *parser)
68 {
69     m_parser = parser;
70     if (m_parser->isSet(QStringLiteral("info"))) {
71         showBackends();
72     }
73     if (parser->isSet(QStringLiteral("json")) || parser->isSet(QStringLiteral("outputs")) || !m_outputArgs.isEmpty()) {
74         KScreen::GetConfigOperation *op = new KScreen::GetConfigOperation();
75         connect(op, &KScreen::GetConfigOperation::finished, this, [this](KScreen::ConfigOperation *op) {
76             configReceived(op);
77         });
78         return;
79     }
80     if (m_parser->isSet(QStringLiteral("dpms"))) {
81         if (!QGuiApplication::platformName().startsWith(QLatin1String("wayland"))) {
82             cerr << "DPMS is only supported on Wayland." << Qt::endl;
83             // We need to kick the event loop, otherwise .quit() hangs
84             QTimer::singleShot(0, qApp->quit);
85             return;
86         }
87 
88         m_dpmsClient = new DpmsClient(this);
89         if (m_parser->isSet(QStringLiteral("dpms-excluded"))) {
90             const auto excludedConnectors = m_parser->values(QStringLiteral("dpms-excluded"));
91             m_dpmsClient->setExcludedOutputNames(excludedConnectors);
92         }
93 
94         connect(m_dpmsClient, &DpmsClient::finished, qApp, &QCoreApplication::quit);
95 
96         const QString dpmsArg = m_parser->value(QStringLiteral("dpms"));
97         if (dpmsArg == QLatin1String("show")) {
98             showDpms();
99         } else {
100             setDpms(dpmsArg);
101         }
102         return;
103     }
104 
105     if (m_parser->isSet(QStringLiteral("log"))) {
106         const QString logmsg = m_parser->value(QStringLiteral("log"));
107         if (!Log::instance()->enabled()) {
108             qCWarning(KSCREEN_DOCTOR) << "Logging is disabled, unset KSCREEN_LOGGING in your environment.";
109         } else {
110             Log::log(logmsg);
111         }
112     }
113     // We need to kick the event loop, otherwise .quit() hangs
114     QTimer::singleShot(0, qApp->quit);
115 }
116 
setDpms(const QString & dpmsArg)117 void KScreen::Doctor::setDpms(const QString &dpmsArg)
118 {
119     qDebug() << "SetDpms: " << dpmsArg;
120     connect(m_dpmsClient, &DpmsClient::ready, this, [this, dpmsArg]() {
121         cout << "DPMS.ready()";
122         if (dpmsArg == QLatin1String("off")) {
123             m_dpmsClient->off();
124         } else if (dpmsArg == QLatin1String("on")) {
125             m_dpmsClient->on();
126         } else {
127             cout << "--dpms argument not understood (" << dpmsArg << ")";
128         }
129     });
130 
131     m_dpmsClient->connect();
132 }
133 
showDpms()134 void Doctor::showDpms()
135 {
136     m_dpmsClient = new DpmsClient(this);
137 
138     connect(m_dpmsClient, &DpmsClient::ready, this, []() {
139         cout << "DPMS.ready()";
140     });
141 
142     m_dpmsClient->connect();
143 }
144 
showBackends() const145 void Doctor::showBackends() const
146 {
147     cout << "Environment: " << Qt::endl;
148     auto env_kscreen_backend = qEnvironmentVariable("KSCREEN_BACKEND", QStringLiteral("[not set]"));
149     cout << "  * KSCREEN_BACKEND           : " << env_kscreen_backend << Qt::endl;
150     auto env_kscreen_backend_inprocess = qEnvironmentVariable("KSCREEN_BACKEND_INPROCESS", QStringLiteral("[not set]"));
151     cout << "  * KSCREEN_BACKEND_INPROCESS : " << env_kscreen_backend_inprocess << Qt::endl;
152     auto env_kscreen_logging = qEnvironmentVariable("KSCREEN_LOGGING", QStringLiteral("[not set]"));
153     cout << "  * KSCREEN_LOGGING           : " << env_kscreen_logging << Qt::endl;
154 
155     cout << "Logging to                : " << (Log::instance()->enabled() ? Log::instance()->logFile() : QStringLiteral("[logging disabled]")) << Qt::endl;
156     const auto backends = BackendManager::instance()->listBackends();
157     auto preferred = BackendManager::instance()->preferredBackend();
158     cout << "Preferred KScreen backend : " << green << preferred.fileName() << cr << Qt::endl;
159     cout << "Available KScreen backends:" << Qt::endl;
160     for (const QFileInfo &f : backends) {
161         auto c = blue;
162         if (preferred == f) {
163             c = green;
164         }
165         cout << "  * " << c << f.fileName() << cr << ": " << f.absoluteFilePath() << Qt::endl;
166     }
167     cout << Qt::endl;
168 }
169 
setOptionList(const QStringList & outputArgs)170 void Doctor::setOptionList(const QStringList &outputArgs)
171 {
172     m_outputArgs = outputArgs;
173 }
174 
parseOutputArgs()175 void Doctor::parseOutputArgs()
176 {
177     // qCDebug(KSCREEN_DOCTOR) << "POSARGS" << m_positionalArgs;
178     for (const QString &op : qAsConst(m_outputArgs)) {
179         auto ops = op.split(QLatin1Char('.'));
180         if (ops.count() > 2) {
181             bool ok;
182             int output_id = -1;
183             if (ops[0] == QLatin1String("output")) {
184                 for (const auto &output : m_config->outputs()) {
185                     if (output->name() == ops[1]) {
186                         output_id = output->id();
187                     }
188                 }
189                 if (output_id == -1) {
190                     output_id = ops[1].toInt(&ok);
191                     if (!ok) {
192                         cerr << "Unable to parse output id: " << ops[1] << Qt::endl;
193                         qApp->exit(3);
194                         return;
195                     }
196                 }
197                 if (ops.count() == 3 && ops[2] == QLatin1String("enable")) {
198                     if (!setEnabled(output_id, true)) {
199                         qApp->exit(1);
200                         return;
201                     };
202                 } else if (ops.count() == 3 && ops[2] == QLatin1String("disable")) {
203                     if (!setEnabled(output_id, false)) {
204                         qApp->exit(1);
205                         return;
206                     };
207                 } else if (ops.count() == 4 && ops[2] == QLatin1String("mode")) {
208                     QString mode_id = ops[3];
209                     // set mode
210                     if (!setMode(output_id, mode_id)) {
211                         qApp->exit(9);
212                         return;
213                     }
214                     qCDebug(KSCREEN_DOCTOR) << "Output" << output_id << "set mode" << mode_id;
215 
216                 } else if (ops.count() == 4 && ops[2] == QLatin1String("position")) {
217                     QStringList _pos = ops[3].split(QLatin1Char(','));
218                     if (_pos.count() != 2) {
219                         qCWarning(KSCREEN_DOCTOR) << "Invalid position:" << ops[3];
220                         qApp->exit(5);
221                         return;
222                     }
223                     int x = _pos[0].toInt(&ok);
224                     int y = _pos[1].toInt(&ok);
225                     if (!ok) {
226                         cerr << "Unable to parse position: " << ops[3] << Qt::endl;
227                         qApp->exit(5);
228                         return;
229                     }
230 
231                     QPoint p(x, y);
232                     qCDebug(KSCREEN_DOCTOR) << "Output position" << p;
233                     if (!setPosition(output_id, p)) {
234                         qApp->exit(1);
235                         return;
236                     }
237                 } else if ((ops.count() == 4 || ops.count() == 5) && ops[2] == QLatin1String("scale")) {
238                     // be lenient about . vs. comma as separator
239                     qreal scale = ops[3].replace(QLatin1Char(','), QLatin1Char('.')).toDouble(&ok);
240                     if (ops.count() == 5) {
241                         const QString dbl = ops[3] + QLatin1String(".") + ops[4];
242                         scale = dbl.toDouble(&ok);
243                     };
244                     // set scale
245                     if (!ok || qFuzzyCompare(scale, 0.0) || !setScale(output_id, scale)) {
246                         qCDebug(KSCREEN_DOCTOR) << "Could not set scale " << scale << " to output " << output_id;
247                         qApp->exit(9);
248                         return;
249                     }
250                 } else if ((ops.count() == 4) && (ops[2] == QLatin1String("orientation") || ops[2] == QStringLiteral("rotation"))) {
251                     const QString _rotation = ops[3].toLower();
252                     bool ok = false;
253                     const QHash<QString, KScreen::Output::Rotation> rotationMap({{QStringLiteral("none"), KScreen::Output::None},
254                                                                                  {QStringLiteral("normal"), KScreen::Output::None},
255                                                                                  {QStringLiteral("left"), KScreen::Output::Left},
256                                                                                  {QStringLiteral("right"), KScreen::Output::Right},
257                                                                                  {QStringLiteral("inverted"), KScreen::Output::Inverted}});
258                     KScreen::Output::Rotation rot = KScreen::Output::None;
259                     // set orientation
260                     if (rotationMap.contains(_rotation)) {
261                         ok = true;
262                         rot = rotationMap[_rotation];
263                     }
264                     if (!ok || !setRotation(output_id, rot)) {
265                         qCDebug(KSCREEN_DOCTOR) << "Could not set orientation " << _rotation << " to output " << output_id;
266                         qApp->exit(9);
267                         return;
268                     }
269                 } else if (ops.count() == 4 && ops[2] == QLatin1String("overscan")) {
270                     const uint32_t overscan = ops[3].toInt();
271                     if (overscan > 100) {
272                         qCWarning(KSCREEN_DOCTOR) << "Wrong input: allowed values for overscan are from 0 to 100";
273                         qApp->exit(9);
274                         return;
275                     }
276                     if (!setOverscan(output_id, overscan)) {
277                         qCDebug(KSCREEN_DOCTOR) << "Could not set overscan " << overscan << " to output " << output_id;
278                         qApp->exit(9);
279                         return;
280                     }
281                 } else if (ops.count() == 4 && ops[2] == QLatin1String("vrrpolicy")) {
282                     const QString _policy = ops[3].toLower();
283                     KScreen::Output::VrrPolicy policy;
284                     if (_policy == QStringLiteral("never")) {
285                         policy = KScreen::Output::VrrPolicy::Never;
286                     } else if (_policy == QStringLiteral("always")) {
287                         policy = KScreen::Output::VrrPolicy::Always;
288                     } else if (_policy == QStringLiteral("automatic")) {
289                         policy = KScreen::Output::VrrPolicy::Automatic;
290                     } else {
291                         qCDebug(KSCREEN_DOCTOR) << "Wrong input: Only allowed values are \"never\", \"always\" and \"automatic\"";
292                         qApp->exit(9);
293                         return;
294                     }
295                     if (!setVrrPolicy(output_id, policy)) {
296                         qCDebug(KSCREEN_DOCTOR) << "Could not set vrr policy " << policy << " to output " << output_id;
297                         qApp->exit(9);
298                         return;
299                     }
300                 } else if (ops.count() == 4 && ops[2] == QLatin1String("rgbrange")) {
301                     const QString _range = ops[3].toLower();
302                     KScreen::Output::RgbRange range;
303                     if (_range == QStringLiteral("automatic")) {
304                         range = KScreen::Output::RgbRange::Automatic;
305                     } else if (_range == QStringLiteral("full")) {
306                         range = KScreen::Output::RgbRange::Full;
307                     } else if (_range == QStringLiteral("limited")) {
308                         range = KScreen::Output::RgbRange::Limited;
309                     } else {
310                         qCDebug(KSCREEN_DOCTOR) << "Wrong input: Only allowed values for rgbrange are \"automatic\", \"full\" and \"limited\"";
311                         qApp->exit(9);
312                         return;
313                     }
314                     if (!setRgbRange(output_id, range)) {
315                         qCDebug(KSCREEN_DOCTOR) << "Could not set rgb range " << range << " to output " << output_id;
316                         qApp->exit(9);
317                         return;
318                     }
319                 } else {
320                     cerr << "Unable to parse arguments: " << op << Qt::endl;
321                     qApp->exit(2);
322                     return;
323                 }
324             }
325         }
326     }
327 }
328 
configReceived(KScreen::ConfigOperation * op)329 void Doctor::configReceived(KScreen::ConfigOperation *op)
330 {
331     m_config = op->config();
332 
333     if (m_parser->isSet(QStringLiteral("json"))) {
334         showJson();
335         qApp->quit();
336     }
337     if (m_parser->isSet(QStringLiteral("outputs"))) {
338         showOutputs();
339         qApp->quit();
340     }
341 
342     parseOutputArgs();
343 
344     if (m_changed) {
345         applyConfig();
346         m_changed = false;
347     }
348 }
349 
outputCount() const350 int Doctor::outputCount() const
351 {
352     if (!m_config) {
353         qCWarning(KSCREEN_DOCTOR) << "Invalid config.";
354         return 0;
355     }
356     return m_config->outputs().count();
357 }
358 
showOutputs() const359 void Doctor::showOutputs() const
360 {
361     if (!m_config) {
362         qCWarning(KSCREEN_DOCTOR) << "Invalid config.";
363         return;
364     }
365 
366     QHash<KScreen::Output::Type, QString> typeString;
367     typeString[KScreen::Output::Unknown] = QStringLiteral("Unknown");
368     typeString[KScreen::Output::VGA] = QStringLiteral("VGA");
369     typeString[KScreen::Output::DVI] = QStringLiteral("DVI");
370     typeString[KScreen::Output::DVII] = QStringLiteral("DVII");
371     typeString[KScreen::Output::DVIA] = QStringLiteral("DVIA");
372     typeString[KScreen::Output::DVID] = QStringLiteral("DVID");
373     typeString[KScreen::Output::HDMI] = QStringLiteral("HDMI");
374     typeString[KScreen::Output::Panel] = QStringLiteral("Panel");
375     typeString[KScreen::Output::TV] = QStringLiteral("TV");
376     typeString[KScreen::Output::TVComposite] = QStringLiteral("TVComposite");
377     typeString[KScreen::Output::TVSVideo] = QStringLiteral("TVSVideo");
378     typeString[KScreen::Output::TVComponent] = QStringLiteral("TVComponent");
379     typeString[KScreen::Output::TVSCART] = QStringLiteral("TVSCART");
380     typeString[KScreen::Output::TVC4] = QStringLiteral("TVC4");
381     typeString[KScreen::Output::DisplayPort] = QStringLiteral("DisplayPort");
382 
383     QCollator collator;
384     collator.setNumericMode(true);
385 
386     for (const auto &output : m_config->outputs()) {
387         cout << green << "Output: " << cr << output->id() << " " << output->name();
388         cout << " " << (output->isEnabled() ? green + QLatin1String("enabled") : red + QLatin1String("disabled"));
389         cout << " " << (output->isConnected() ? green + QLatin1String("connected") : red + QLatin1String("disconnected"));
390         cout << " " << (output->isPrimary() ? green + QLatin1String("primary") : QString());
391         auto _type = typeString[output->type()];
392         cout << " " << yellow << (_type.isEmpty() ? QStringLiteral("UnmappedOutputType") : _type);
393         cout << blue << " Modes: " << cr;
394 
395         const auto modes = output->modes();
396         auto modeKeys = modes.keys();
397         std::sort(modeKeys.begin(), modeKeys.end(), collator);
398 
399         for (const auto &key : modeKeys) {
400             auto mode = *modes.find(key);
401 
402             auto name = QStringLiteral("%1x%2@%3")
403                             .arg(QString::number(mode->size().width()), QString::number(mode->size().height()), QString::number(qRound(mode->refreshRate())));
404             if (mode == output->currentMode()) {
405                 name = green + name + QLatin1Char('*') + cr;
406             }
407             if (mode == output->preferredMode()) {
408                 name = name + QLatin1Char('!');
409             }
410             cout << mode->id() << ":" << name << " ";
411         }
412         const auto g = output->geometry();
413         cout << yellow << "Geometry: " << cr << g.x() << "," << g.y() << " " << g.width() << "x" << g.height() << " ";
414         cout << yellow << "Scale: " << cr << output->scale() << " ";
415         cout << yellow << "Rotation: " << cr << output->rotation() << " ";
416         cout << yellow << "Overscan: " << cr << output->overscan() << " ";
417         cout << yellow << "Vrr: ";
418         if (output->capabilities() & Output::Capability::Vrr) {
419             switch (output->vrrPolicy()) {
420             case Output::VrrPolicy::Never:
421                 cout << cr << "Never ";
422                 break;
423             case Output::VrrPolicy::Automatic:
424                 cout << cr << "Automatic ";
425                 break;
426             case Output::VrrPolicy::Always:
427                 cout << cr << "Always ";
428             }
429         } else {
430             cout << cr << "incapable ";
431         }
432         cout << yellow << "RgbRange: ";
433         if (output->capabilities() & Output::Capability::RgbRange) {
434             switch (output->rgbRange()) {
435             case Output::RgbRange::Automatic:
436                 cout << cr << "Automatic ";
437                 break;
438             case Output::RgbRange::Full:
439                 cout << cr << "Full ";
440                 break;
441             case Output::RgbRange::Limited:
442                 cout << cr << "Limited ";
443             }
444         } else {
445             cout << cr << "unknown ";
446         }
447         if (output->isPrimary()) {
448             cout << blue << "primary";
449         }
450         cout << cr << Qt::endl;
451     }
452 }
453 
showJson() const454 void Doctor::showJson() const
455 {
456     QJsonDocument doc(KScreen::ConfigSerializer::serializeConfig(m_config));
457     cout << doc.toJson(QJsonDocument::Indented);
458 }
459 
setEnabled(int id,bool enabled=true)460 bool Doctor::setEnabled(int id, bool enabled = true)
461 {
462     if (!m_config) {
463         qCWarning(KSCREEN_DOCTOR) << "Invalid config.";
464         return false;
465     }
466 
467     for (const auto &output : m_config->outputs()) {
468         if (output->id() == id) {
469             cout << (enabled ? "Enabling " : "Disabling ") << "output " << id << Qt::endl;
470             output->setEnabled(enabled);
471             m_changed = true;
472             return true;
473         }
474     }
475     cerr << "Output with id " << id << " not found." << Qt::endl;
476     qApp->exit(8);
477     return false;
478 }
479 
setPosition(int id,const QPoint & pos)480 bool Doctor::setPosition(int id, const QPoint &pos)
481 {
482     if (!m_config) {
483         qCWarning(KSCREEN_DOCTOR) << "Invalid config.";
484         return false;
485     }
486 
487     for (const auto &output : m_config->outputs()) {
488         if (output->id() == id) {
489             qCDebug(KSCREEN_DOCTOR) << "Set output position" << pos;
490             output->setPos(pos);
491             m_changed = true;
492             return true;
493         }
494     }
495     cout << "Output with id " << id << " not found." << Qt::endl;
496     return false;
497 }
498 
setMode(int id,const QString & mode_id)499 bool Doctor::setMode(int id, const QString &mode_id)
500 {
501     if (!m_config) {
502         qCWarning(KSCREEN_DOCTOR) << "Invalid config.";
503         return false;
504     }
505 
506     for (const auto &output : m_config->outputs()) {
507         if (output->id() == id) {
508             // find mode
509             for (const KScreen::ModePtr mode : output->modes()) {
510                 auto name =
511                     QStringLiteral("%1x%2@%3")
512                         .arg(QString::number(mode->size().width()), QString::number(mode->size().height()), QString::number(qRound(mode->refreshRate())));
513                 if (mode->id() == mode_id || name == mode_id) {
514                     qCDebug(KSCREEN_DOCTOR) << "Taddaaa! Found mode" << mode->id() << name;
515                     output->setCurrentModeId(mode->id());
516                     m_changed = true;
517                     return true;
518                 }
519             }
520         }
521     }
522     cout << "Output mode " << mode_id << " not found." << Qt::endl;
523     return false;
524 }
525 
setScale(int id,qreal scale)526 bool Doctor::setScale(int id, qreal scale)
527 {
528     if (!m_config) {
529         qCWarning(KSCREEN_DOCTOR) << "Invalid config.";
530         return false;
531     }
532 
533     for (const auto &output : m_config->outputs()) {
534         if (output->id() == id) {
535             output->setScale(scale);
536             m_changed = true;
537             return true;
538         }
539     }
540     cout << "Output scale " << id << " invalid." << Qt::endl;
541     return false;
542 }
543 
setRotation(int id,KScreen::Output::Rotation rot)544 bool Doctor::setRotation(int id, KScreen::Output::Rotation rot)
545 {
546     if (!m_config) {
547         qCWarning(KSCREEN_DOCTOR) << "Invalid config.";
548         return false;
549     }
550 
551     for (const auto &output : m_config->outputs()) {
552         if (output->id() == id) {
553             output->setRotation(rot);
554             m_changed = true;
555             return true;
556         }
557     }
558     cout << "Output rotation " << id << " invalid." << Qt::endl;
559     return false;
560 }
561 
setOverscan(int id,uint32_t overscan)562 bool Doctor::setOverscan(int id, uint32_t overscan)
563 {
564     if (!m_config) {
565         qCWarning(KSCREEN_DOCTOR) << "Invalid config.";
566         return false;
567     }
568 
569     for (const auto &output : m_config->outputs()) {
570         if (output->id() == id) {
571             output->setOverscan(overscan);
572             m_changed = true;
573             return true;
574         }
575     }
576     cout << "Output overscan " << id << " invalid." << Qt::endl;
577     return false;
578 }
579 
setVrrPolicy(int id,KScreen::Output::VrrPolicy policy)580 bool Doctor::setVrrPolicy(int id, KScreen::Output::VrrPolicy policy)
581 {
582     if (!m_config) {
583         qCWarning(KSCREEN_DOCTOR) << "Invalid config.";
584         return false;
585     }
586 
587     for (const auto &output : m_config->outputs()) {
588         if (output->id() == id) {
589             output->setVrrPolicy(policy);
590             m_changed = true;
591             return true;
592         }
593     }
594     cout << "Output VrrPolicy " << id << " invalid." << Qt::endl;
595     return false;
596 }
597 
setRgbRange(int id,KScreen::Output::RgbRange rgbRange)598 bool Doctor::setRgbRange(int id, KScreen::Output::RgbRange rgbRange)
599 {
600     if (!m_config) {
601         qCWarning(KSCREEN_DOCTOR) << "Invalid config.";
602         return false;
603     }
604 
605     for (const auto &output : m_config->outputs()) {
606         if (output->id() == id) {
607             output->setRgbRange(rgbRange);
608             m_changed = true;
609             return true;
610         }
611     }
612     cout << "Output RgbRange " << id << " invalid." << Qt::endl;
613     return false;
614 }
615 
applyConfig()616 void Doctor::applyConfig()
617 {
618     if (!m_changed) {
619         return;
620     }
621     auto setop = new SetConfigOperation(m_config, this);
622     setop->exec();
623     qCDebug(KSCREEN_DOCTOR) << "setop exec returned" << m_config;
624     qApp->exit(0);
625 }
626