File indexing completed on 2024-05-12 05:33:54

0001 /*
0002  *  SPDX-FileCopyrightText: 2014-2016 Sebastian Kügler <sebas@kde.org>
0003  *
0004  *  SPDX-License-Identifier: LGPL-2.1-or-later
0005  */
0006 
0007 #include "doctor.h"
0008 #include "mode.h"
0009 #include <dpms.h>
0010 
0011 #include <QCollator>
0012 #include <QCoreApplication>
0013 #include <QDateTime>
0014 #include <QFile>
0015 #include <QGuiApplication>
0016 #include <QJsonArray>
0017 #include <QJsonDocument>
0018 #include <QJsonObject>
0019 #include <QLoggingCategory>
0020 #include <QRect>
0021 #include <QScreen>
0022 #include <QStandardPaths>
0023 
0024 #include <utility>
0025 
0026 #include "../backendmanager_p.h"
0027 #include "../config.h"
0028 #include "../configoperation.h"
0029 #include "../getconfigoperation.h"
0030 #include "../log.h"
0031 #include "../output.h"
0032 #include "../setconfigoperation.h"
0033 
0034 Q_LOGGING_CATEGORY(KSCREEN_DOCTOR, "kscreen.doctor")
0035 
0036 static QTextStream cout(stdout);
0037 static QTextStream cerr(stderr);
0038 
0039 const static QString green = QStringLiteral("\033[01;32m");
0040 const static QString red = QStringLiteral("\033[01;31m");
0041 const static QString yellow = QStringLiteral("\033[01;33m");
0042 const static QString blue = QStringLiteral("\033[01;34m");
0043 const static QString bold = QStringLiteral("\033[01;39m");
0044 const static QString cr = QStringLiteral("\033[0;0m");
0045 
0046 namespace KScreen
0047 {
0048 namespace ConfigSerializer
0049 {
0050 // Exported private symbol in configserializer_p.h in KScreen
0051 extern QJsonObject serializeConfig(const KScreen::ConfigPtr &config);
0052 }
0053 }
0054 
0055 using namespace KScreen;
0056 
0057 Doctor::Doctor(QObject *parent)
0058     : QObject(parent)
0059     , m_config(nullptr)
0060     , m_changed(false)
0061     , m_dpmsClient(nullptr)
0062 {
0063 }
0064 
0065 Doctor::~Doctor()
0066 {
0067 }
0068 
0069 void Doctor::start(QCommandLineParser *parser)
0070 {
0071     m_parser = parser;
0072     if (m_parser->isSet(QStringLiteral("info"))) {
0073         showBackends();
0074     }
0075     if (parser->isSet(QStringLiteral("json")) || parser->isSet(QStringLiteral("outputs")) || !m_outputArgs.isEmpty()) {
0076         KScreen::GetConfigOperation *op = new KScreen::GetConfigOperation();
0077         connect(op, &KScreen::GetConfigOperation::finished, this, [this](KScreen::ConfigOperation *op) {
0078             configReceived(op);
0079         });
0080         return;
0081     }
0082     if (m_parser->isSet(QStringLiteral("dpms"))) {
0083         if (!QGuiApplication::platformName().startsWith(QLatin1String("wayland"))) {
0084             cerr << "DPMS is only supported on Wayland." << Qt::endl;
0085             // We need to kick the event loop, otherwise .quit() hangs
0086             QTimer::singleShot(0, qApp->quit);
0087             return;
0088         }
0089 
0090         m_dpmsClient = new Dpms(this);
0091         auto screens = qGuiApp->screens();
0092         if (m_parser->isSet(QStringLiteral("dpms-excluded"))) {
0093             const auto excludedConnectors = m_parser->values(QStringLiteral("dpms-excluded"));
0094             auto it = std::remove_if(screens.begin(), screens.end(), [&excludedConnectors](QScreen *screen) {
0095                 return excludedConnectors.contains(screen->name());
0096             });
0097             screens.erase(it, screens.end());
0098         }
0099 
0100         connect(m_dpmsClient, &Dpms::hasPendingChangesChanged, qGuiApp, [](bool hasChanges) {
0101             if (!hasChanges) {
0102                 // We need to hit the event loop, otherwise .quit() hangs
0103                 QTimer::singleShot(0, qApp->quit);
0104             }
0105         });
0106 
0107         const QString dpmsArg = m_parser->value(QStringLiteral("dpms"));
0108         if (dpmsArg == QLatin1String("show")) {
0109         } else {
0110             if (!m_dpmsClient->isSupported()) {
0111                 cerr << "DPMS not supported in this system";
0112                 qGuiApp->quit();
0113                 return;
0114             }
0115 
0116             if (dpmsArg == QLatin1String("off")) {
0117                 m_dpmsClient->switchMode(KScreen::Dpms::Off, screens);
0118             } else if (dpmsArg == QLatin1String("on")) {
0119                 m_dpmsClient->switchMode(KScreen::Dpms::On, screens);
0120             } else {
0121                 cerr << "--dpms argument not understood (" << dpmsArg << ")";
0122             }
0123         }
0124         return;
0125     }
0126 
0127     if (m_parser->isSet(QStringLiteral("log"))) {
0128         const QString logmsg = m_parser->value(QStringLiteral("log"));
0129         if (!Log::instance()->enabled()) {
0130             qCWarning(KSCREEN_DOCTOR) << "Logging is disabled, unset KSCREEN_LOGGING in your environment.";
0131         } else {
0132             Log::log(logmsg);
0133         }
0134     }
0135     // We need to kick the event loop, otherwise .quit() hangs
0136     QTimer::singleShot(0, qApp->quit);
0137 }
0138 
0139 void Doctor::showBackends() const
0140 {
0141     cout << "Environment: " << Qt::endl;
0142     auto env_kscreen_backend = qEnvironmentVariable("KSCREEN_BACKEND", QStringLiteral("[not set]"));
0143     cout << "  * KSCREEN_BACKEND           : " << env_kscreen_backend << Qt::endl;
0144     auto env_kscreen_backend_inprocess = qEnvironmentVariable("KSCREEN_BACKEND_INPROCESS", QStringLiteral("[not set]"));
0145     cout << "  * KSCREEN_BACKEND_INPROCESS : " << env_kscreen_backend_inprocess << Qt::endl;
0146     auto env_kscreen_logging = qEnvironmentVariable("KSCREEN_LOGGING", QStringLiteral("[not set]"));
0147     cout << "  * KSCREEN_LOGGING           : " << env_kscreen_logging << Qt::endl;
0148 
0149     cout << "Logging to                : " << (Log::instance()->enabled() ? Log::instance()->logFile() : QStringLiteral("[logging disabled]")) << Qt::endl;
0150     const auto backends = BackendManager::instance()->listBackends();
0151     auto preferred = BackendManager::instance()->preferredBackend();
0152     cout << "Preferred KScreen backend : " << green << preferred.fileName() << cr << Qt::endl;
0153     cout << "Available KScreen backends:" << Qt::endl;
0154     for (const QFileInfo &f : backends) {
0155         auto c = blue;
0156         if (preferred == f) {
0157             c = green;
0158         }
0159         cout << "  * " << c << f.fileName() << cr << ": " << f.absoluteFilePath() << Qt::endl;
0160     }
0161     cout << Qt::endl;
0162 }
0163 
0164 void Doctor::setOptionList(const QStringList &outputArgs)
0165 {
0166     m_outputArgs = outputArgs;
0167 }
0168 
0169 OutputPtr Doctor::findOutput(const QString &query)
0170 {
0171     // try as an output name or ID
0172     for (const auto &output : m_config->outputs()) {
0173         if (output->name() == query) {
0174             return output;
0175         }
0176     }
0177     bool ok;
0178     int id = query.toInt(&ok);
0179     if (!ok) {
0180         cerr << "Output with name " << query << " not found." << Qt::endl;
0181         return OutputPtr();
0182     }
0183 
0184     if (m_config->outputs().contains(id)) {
0185         return m_config->outputs()[id];
0186     } else {
0187         cerr << "Output with id " << id << " not found." << Qt::endl;
0188         return OutputPtr();
0189     }
0190 }
0191 
0192 void Doctor::parseOutputArgs()
0193 {
0194     // qCDebug(KSCREEN_DOCTOR) << "POSARGS" << m_positionalArgs;
0195     for (const QString &op : std::as_const(m_outputArgs)) {
0196         auto ops = op.split(QLatin1Char('.'));
0197         if (ops.count() > 2) {
0198             bool ok;
0199             if (ops[0] == QLatin1String("output")) {
0200                 OutputPtr output = findOutput(ops[1]);
0201                 if (!output) {
0202                     qApp->exit(3);
0203                     return;
0204                 }
0205                 int output_id = output->id();
0206 
0207                 const QString subcmd = ops.length() > 2 ? ops[2] : QString();
0208 
0209                 if (ops.count() == 3 && subcmd == QLatin1String("primary")) {
0210                     setPrimary(output);
0211                 } else if (ops.count() == 4 && subcmd == QLatin1String("priority")) {
0212                     uint32_t priority = ops[3].toUInt(&ok);
0213                     if (!ok || priority > 100) {
0214                         qCWarning(KSCREEN_DOCTOR) << "Wrong input: allowed values for priority are from 1 to 100";
0215                         qApp->exit(5);
0216                         return;
0217                     }
0218                     setPriority(output, priority);
0219                 } else if (ops.count() == 3 && subcmd == QLatin1String("enable")) {
0220                     setEnabled(output, true);
0221                 } else if (ops.count() == 3 && subcmd == QLatin1String("disable")) {
0222                     setEnabled(output, false);
0223                 } else if (ops.count() == 4 && subcmd == QLatin1String("mode")) {
0224                     QString mode_id = ops[3];
0225                     // set mode
0226                     if (!setMode(output, mode_id)) {
0227                         qApp->exit(9);
0228                         return;
0229                     }
0230                     qCDebug(KSCREEN_DOCTOR) << "Output" << output_id << "set mode" << mode_id;
0231 
0232                 } else if (ops.count() == 4 && subcmd == QLatin1String("position")) {
0233                     QStringList _pos = ops[3].split(QLatin1Char(','));
0234                     if (_pos.count() != 2) {
0235                         qCWarning(KSCREEN_DOCTOR) << "Invalid position:" << ops[3];
0236                         qApp->exit(5);
0237                         return;
0238                     }
0239                     int x = _pos[0].toInt(&ok);
0240                     int y = _pos[1].toInt(&ok);
0241                     if (!ok) {
0242                         cerr << "Unable to parse position: " << ops[3] << Qt::endl;
0243                         qApp->exit(5);
0244                         return;
0245                     }
0246 
0247                     QPoint p(x, y);
0248                     qCDebug(KSCREEN_DOCTOR) << "Output position" << p;
0249                     setPosition(output, p);
0250 
0251                 } else if ((ops.count() == 4 || ops.count() == 5) && subcmd == QLatin1String("scale")) {
0252                     // be lenient about . vs. comma as separator
0253                     qreal scale = ops[3].replace(QLatin1Char(','), QLatin1Char('.')).toDouble(&ok);
0254                     if (ops.count() == 5) {
0255                         const QString dbl = ops[3] + QStringLiteral(".") + ops[4];
0256                         scale = dbl.toDouble(&ok);
0257                     };
0258                     // set scale
0259                     if (!ok || qFuzzyCompare(scale, 0.0)) {
0260                         qCDebug(KSCREEN_DOCTOR) << "Could not set scale " << scale << " to output " << output_id;
0261                         qApp->exit(9);
0262                         return;
0263                     }
0264                     setScale(output, scale);
0265                 } else if ((ops.count() == 4) && (subcmd == QLatin1String("orientation") || subcmd == QStringLiteral("rotation"))) {
0266                     const QString _rotation = ops[3].toLower();
0267                     bool ok = false;
0268                     const QHash<QString, KScreen::Output::Rotation> rotationMap({{QStringLiteral("none"), KScreen::Output::None},
0269                                                                                  {QStringLiteral("normal"), KScreen::Output::None},
0270                                                                                  {QStringLiteral("left"), KScreen::Output::Left},
0271                                                                                  {QStringLiteral("right"), KScreen::Output::Right},
0272                                                                                  {QStringLiteral("inverted"), KScreen::Output::Inverted},
0273                                                                                  {QStringLiteral("flipped"), KScreen::Output::Flipped},
0274                                                                                  {QStringLiteral("flipped90"), KScreen::Output::Flipped90},
0275                                                                                  {QStringLiteral("flipped180"), KScreen::Output::Flipped180},
0276                                                                                  {QStringLiteral("flipped270"), KScreen::Output::Flipped270}});
0277                     KScreen::Output::Rotation rot = KScreen::Output::None;
0278                     // set orientation
0279                     if (rotationMap.contains(_rotation)) {
0280                         ok = true;
0281                         rot = rotationMap[_rotation];
0282                     }
0283                     if (!ok) {
0284                         qCDebug(KSCREEN_DOCTOR) << "Could not set orientation " << _rotation << " to output " << output_id;
0285                         qApp->exit(9);
0286                         return;
0287                     }
0288                     setRotation(output, rot);
0289                 } else if (ops.count() == 4 && subcmd == QLatin1String("overscan")) {
0290                     const uint32_t overscan = ops[3].toInt();
0291                     if (overscan > 100) {
0292                         qCWarning(KSCREEN_DOCTOR) << "Wrong input: allowed values for overscan are from 0 to 100";
0293                         qApp->exit(9);
0294                         return;
0295                     }
0296                     setOverscan(output, overscan);
0297                 } else if (ops.count() == 4 && subcmd == QLatin1String("vrrpolicy")) {
0298                     const QString _policy = ops[3].toLower();
0299                     KScreen::Output::VrrPolicy policy;
0300                     if (_policy == QStringLiteral("never")) {
0301                         policy = KScreen::Output::VrrPolicy::Never;
0302                     } else if (_policy == QStringLiteral("always")) {
0303                         policy = KScreen::Output::VrrPolicy::Always;
0304                     } else if (_policy == QStringLiteral("automatic")) {
0305                         policy = KScreen::Output::VrrPolicy::Automatic;
0306                     } else {
0307                         qCDebug(KSCREEN_DOCTOR) << "Wrong input: Only allowed values are \"never\", \"always\" and \"automatic\"";
0308                         qApp->exit(9);
0309                         return;
0310                     }
0311                     setVrrPolicy(output, policy);
0312                 } else if (ops.count() == 4 && subcmd == QLatin1String("rgbrange")) {
0313                     const QString _range = ops[3].toLower();
0314                     KScreen::Output::RgbRange range;
0315                     if (_range == QStringLiteral("automatic")) {
0316                         range = KScreen::Output::RgbRange::Automatic;
0317                     } else if (_range == QStringLiteral("full")) {
0318                         range = KScreen::Output::RgbRange::Full;
0319                     } else if (_range == QStringLiteral("limited")) {
0320                         range = KScreen::Output::RgbRange::Limited;
0321                     } else {
0322                         qCDebug(KSCREEN_DOCTOR) << "Wrong input: Only allowed values for rgbrange are \"automatic\", \"full\" and \"limited\"";
0323                         qApp->exit(9);
0324                         return;
0325                     }
0326                     setRgbRange(output, range);
0327                 } else if (ops.count() == 4 && subcmd == "hdr") {
0328                     const QString _enable = ops[3].toLower();
0329                     if (_enable == "enable") {
0330                         setHdrEnabled(output, true);
0331                     } else if (_enable == "disable") {
0332                         setHdrEnabled(output, false);
0333                     } else {
0334                         qCDebug(KSCREEN_DOCTOR) << "Wrong input: Only allowed values for hdr are \"enable\" and \"disable\"";
0335                         qApp->exit(9);
0336                         return;
0337                     }
0338                 } else if (ops.count() == 4 && subcmd == "sdr-brightness") {
0339                     const uint32_t brightness = ops[3].toInt();
0340                     if (brightness < 100 || brightness > 10000) {
0341                         qCDebug(KSCREEN_DOCTOR) << "Wrong input: Allowed range for sdr-brightness is 100 to 10000";
0342                         qApp->exit(9);
0343                         return;
0344                     }
0345                     setSdrBrightness(output, brightness);
0346                 } else if (ops.count() == 4 && subcmd == "wcg") {
0347                     const QString _enable = ops[3].toLower();
0348                     if (_enable == "enable") {
0349                         setWcgEnabled(output, true);
0350                     } else if (_enable == "disable") {
0351                         setWcgEnabled(output, false);
0352                     } else {
0353                         qCDebug(KSCREEN_DOCTOR) << "Wrong input: Only allowed values for wcg are \"enable\" and \"disable\"";
0354                         qApp->exit(9);
0355                         return;
0356                     }
0357                 } else if (ops.count() >= 4 && subcmd == "iccprofile") {
0358                     QString profilePath = ops[3];
0359                     for (uint32_t i = 4; i < ops.size(); i++) {
0360                         profilePath += "." + ops[i];
0361                     }
0362                     output->setIccProfilePath(profilePath);
0363                     m_changed = true;
0364                 } else if (ops.count() >= 4 && subcmd == "sdrGamut") {
0365                     const uint32_t wideness = ops[3].toUInt();
0366                     if (wideness > 100) {
0367                         qCDebug(KSCREEN_DOCTOR) << "Wrong input: Allowed range for sdr wideness is 0 to 100";
0368                         qApp->exit(9);
0369                         return;
0370                     }
0371                     output->setSdrGamutWideness(wideness / 100.0);
0372                     m_changed = true;
0373                 } else if (ops.count() >= 4 && subcmd == "maxBrightnessOverride") {
0374                     if (ops[3] == "disable") {
0375                         output->setMaxPeakBrightnessOverride(std::nullopt);
0376                     } else if (const uint32_t nits = ops[3].toUInt(); nits != 0) {
0377                         output->setMaxPeakBrightnessOverride(nits);
0378                     } else {
0379                         qCDebug(KSCREEN_DOCTOR) << "Wrong input: max brightness must be bigger than 0";
0380                         qApp->exit(9);
0381                         return;
0382                     }
0383                     m_changed = true;
0384                 } else if (ops.count() >= 4 && subcmd == "maxAverageBrightnessOverride") {
0385                     if (ops[3] == "disable") {
0386                         output->setMaxPeakBrightnessOverride(std::nullopt);
0387                     } else if (const uint32_t nits = ops[3].toUInt(); nits != 0) {
0388                         output->setMaxAverageBrightnessOverride(nits);
0389                     } else {
0390                         qCDebug(KSCREEN_DOCTOR) << "Wrong input: max average brightness must be bigger than 0";
0391                         qApp->exit(9);
0392                         return;
0393                     }
0394                     m_changed = true;
0395                 } else if (ops.count() >= 4 && subcmd == "minBrightnessOverride") {
0396                     if (ops[3] == "disable") {
0397                         output->setMinBrightnessOverride(std::nullopt);
0398                     } else if (const uint32_t nits10k = ops[3].toUInt(); nits10k != 0) {
0399                         output->setMinBrightnessOverride(nits10k / 10'000.0);
0400                     } else {
0401                         qCDebug(KSCREEN_DOCTOR) << "Wrong input: max average brightness must be bigger than 0";
0402                         qApp->exit(9);
0403                         return;
0404                     }
0405                     m_changed = true;
0406                 } else {
0407                     cerr << "Unable to parse arguments: " << op << Qt::endl;
0408                     qApp->exit(2);
0409                     return;
0410                 }
0411             }
0412         }
0413     }
0414 }
0415 
0416 void Doctor::configReceived(KScreen::ConfigOperation *op)
0417 {
0418     m_config = op->config();
0419 
0420     if (!m_config) {
0421         qCWarning(KSCREEN_DOCTOR) << "Invalid config.";
0422         return;
0423     }
0424 
0425     if (m_parser->isSet(QStringLiteral("json"))) {
0426         showJson();
0427         qApp->quit();
0428     }
0429     if (m_parser->isSet(QStringLiteral("outputs"))) {
0430         showOutputs();
0431         qApp->quit();
0432     }
0433 
0434     parseOutputArgs();
0435 
0436     if (m_changed) {
0437         applyConfig();
0438         m_changed = false;
0439     }
0440 }
0441 
0442 void Doctor::showOutputs() const
0443 {
0444     QHash<KScreen::Output::Type, QString> typeString;
0445     typeString[KScreen::Output::Unknown] = QStringLiteral("Unknown");
0446     typeString[KScreen::Output::VGA] = QStringLiteral("VGA");
0447     typeString[KScreen::Output::DVI] = QStringLiteral("DVI");
0448     typeString[KScreen::Output::DVII] = QStringLiteral("DVII");
0449     typeString[KScreen::Output::DVIA] = QStringLiteral("DVIA");
0450     typeString[KScreen::Output::DVID] = QStringLiteral("DVID");
0451     typeString[KScreen::Output::HDMI] = QStringLiteral("HDMI");
0452     typeString[KScreen::Output::Panel] = QStringLiteral("Panel");
0453     typeString[KScreen::Output::TV] = QStringLiteral("TV");
0454     typeString[KScreen::Output::TVComposite] = QStringLiteral("TVComposite");
0455     typeString[KScreen::Output::TVSVideo] = QStringLiteral("TVSVideo");
0456     typeString[KScreen::Output::TVComponent] = QStringLiteral("TVComponent");
0457     typeString[KScreen::Output::TVSCART] = QStringLiteral("TVSCART");
0458     typeString[KScreen::Output::TVC4] = QStringLiteral("TVC4");
0459     typeString[KScreen::Output::DisplayPort] = QStringLiteral("DisplayPort");
0460 
0461     QCollator collator;
0462     collator.setNumericMode(true);
0463 
0464     for (const auto &output : m_config->outputs()) {
0465         const auto endl = '\n';
0466         cout << green << "Output: " << cr << output->id() << " " << output->name() << endl;
0467         cout << "\t" << (output->isEnabled() ? green + QStringLiteral("enabled") : red + QStringLiteral("disabled")) << cr << endl;
0468         cout << "\t" << (output->isConnected() ? green + QStringLiteral("connected") : red + QStringLiteral("disconnected")) << cr << endl;
0469         cout << "\t" << (output->isEnabled() ? green : red) + QStringLiteral("priority ") << output->priority() << cr << endl;
0470         auto _type = typeString[output->type()];
0471         cout << "\t" << yellow << (_type.isEmpty() ? QStringLiteral("UnmappedOutputType") : _type) << endl;
0472         cout << blue << "\tModes: " << cr;
0473 
0474         const auto modes = output->modes();
0475         auto modeKeys = modes.keys();
0476         std::sort(modeKeys.begin(), modeKeys.end(), collator);
0477 
0478         for (const auto &key : modeKeys) {
0479             auto mode = *modes.find(key);
0480 
0481             auto name = QStringLiteral("%1x%2@%3")
0482                             .arg(QString::number(mode->size().width()), QString::number(mode->size().height()), QString::number(qRound(mode->refreshRate())));
0483             if (mode == output->currentMode()) {
0484                 name = green + name + QLatin1Char('*') + cr;
0485             }
0486             if (mode == output->preferredMode()) {
0487                 name = name + QLatin1Char('!');
0488             }
0489             cout << " " << mode->id() << ":" << name << " ";
0490         }
0491         cout << endl;
0492         const auto g = output->geometry();
0493         cout << yellow << "\tGeometry: " << cr << g.x() << "," << g.y() << " " << g.width() << "x" << g.height() << endl;
0494         cout << yellow << "\tScale: " << cr << output->scale() << endl;
0495         cout << yellow << "\tRotation: " << cr << output->rotation() << endl;
0496         cout << yellow << "\tOverscan: " << cr << output->overscan() << endl;
0497         cout << yellow << "\tVrr: ";
0498         if (output->capabilities() & Output::Capability::Vrr) {
0499             switch (output->vrrPolicy()) {
0500             case Output::VrrPolicy::Never:
0501                 cout << cr << "Never" << endl;
0502                 break;
0503             case Output::VrrPolicy::Automatic:
0504                 cout << cr << "Automatic" << endl;
0505                 break;
0506             case Output::VrrPolicy::Always:
0507                 cout << cr << "Always" << endl;
0508             }
0509         } else {
0510             cout << cr << "incapable" << endl;
0511         }
0512         cout << yellow << "\tRgbRange: ";
0513         if (output->capabilities() & Output::Capability::RgbRange) {
0514             switch (output->rgbRange()) {
0515             case Output::RgbRange::Automatic:
0516                 cout << cr << "Automatic" << endl;
0517                 break;
0518             case Output::RgbRange::Full:
0519                 cout << cr << "Full" << endl;
0520                 break;
0521             case Output::RgbRange::Limited:
0522                 cout << cr << "Limited" << endl;
0523             }
0524         } else {
0525             cout << cr << "unknown" << endl;
0526         }
0527         cout << yellow << "\tHDR: ";
0528         if (output->capabilities() & Output::Capability::HighDynamicRange) {
0529             if (output->isHdrEnabled()) {
0530                 cout << cr << "enabled" << endl;
0531                 cout << yellow << "\t\tSDR brightness: " << cr << output->sdrBrightness() << " nits" << endl;
0532                 cout << yellow << "\t\tSDR gamut wideness: " << cr << std::round(output->sdrGamutWideness() * 100) << "%" << endl;
0533                 cout << yellow << "\t\tPeak brightness: " << cr << output->maxPeakBrightness() << " nits";
0534                 if (const auto used = output->maxPeakBrightnessOverride()) {
0535                     cout << yellow << ", overridden with: " << cr << *used << " nits";
0536                 }
0537                 cout << endl;
0538                 cout << yellow << "\t\tMax average brightness: " << cr << output->maxAverageBrightness() << " nits";
0539                 if (const auto used = output->maxAverageBrightnessOverride()) {
0540                     cout << yellow << ", overridden with: " << cr << *used << " nits";
0541                 }
0542                 cout << endl;
0543                 cout << yellow << "\t\tMin brightness: " << cr << output->minBrightness() << " nits";
0544                 if (const auto used = output->minBrightnessOverride()) {
0545                     cout << yellow << ", overridden with: " << cr << (*used) / 10'000.0 << " nits";
0546                 }
0547                 cout << endl;
0548             } else {
0549                 cout << cr << "disabled" << endl;
0550             }
0551         } else {
0552             cout << cr << "incapable" << endl;
0553         }
0554         cout << yellow << "\tWide Color Gamut: ";
0555         if (output->capabilities() & Output::Capability::WideColorGamut) {
0556             if (output->isWcgEnabled()) {
0557                 cout << cr << "enabled" << endl;
0558             } else {
0559                 cout << cr << "disabled" << endl;
0560             }
0561         } else {
0562             cout << cr << "incapable" << endl;
0563         }
0564         cout << yellow << "\tICC profile: ";
0565         if (output->capabilities() & Output::Capability::IccProfile) {
0566             if (!output->iccProfilePath().isEmpty()) {
0567                 cout << cr << output->iccProfilePath() << endl;
0568             } else {
0569                 cout << cr << "none" << endl;
0570             }
0571         } else {
0572             cout << cr << "incapable" << endl;
0573         }
0574     }
0575 }
0576 
0577 void Doctor::showJson() const
0578 {
0579     QJsonDocument doc(KScreen::ConfigSerializer::serializeConfig(m_config));
0580     cout << doc.toJson(QJsonDocument::Indented);
0581 }
0582 
0583 void Doctor::setEnabled(OutputPtr output, bool enable)
0584 {
0585     cout << (enable ? "Enabling " : "Disabling ") << "output " << output->id() << Qt::endl;
0586     output->setEnabled(enable);
0587     m_changed = true;
0588 }
0589 
0590 void Doctor::setPosition(OutputPtr output, const QPoint &pos)
0591 {
0592     qCDebug(KSCREEN_DOCTOR) << "Set output position" << pos;
0593     output->setPos(pos);
0594     m_changed = true;
0595 }
0596 
0597 KScreen::ModePtr Doctor::findMode(OutputPtr output, const QString &query)
0598 {
0599     for (const KScreen::ModePtr &mode : output->modes()) {
0600         auto name = QStringLiteral("%1x%2@%3")
0601                         .arg(QString::number(mode->size().width()), QString::number(mode->size().height()), QString::number(qRound(mode->refreshRate())));
0602         if (mode->id() == query || name == query) {
0603             qCDebug(KSCREEN_DOCTOR) << "Taddaaa! Found mode" << mode->id() << name;
0604             return mode;
0605         }
0606     }
0607     cout << "Output mode " << query << " not found." << Qt::endl;
0608     return ModePtr();
0609 }
0610 
0611 bool Doctor::setMode(OutputPtr output, const QString &query)
0612 {
0613     // find mode
0614     const KScreen::ModePtr mode = findMode(output, query);
0615     if (!mode) {
0616         return false;
0617     }
0618     output->setCurrentModeId(mode->id());
0619     m_changed = true;
0620     return true;
0621 }
0622 
0623 void Doctor::setScale(OutputPtr output, qreal scale)
0624 {
0625     output->setScale(scale);
0626     m_changed = true;
0627 }
0628 
0629 void Doctor::setRotation(OutputPtr output, KScreen::Output::Rotation rot)
0630 {
0631     output->setRotation(rot);
0632     m_changed = true;
0633 }
0634 
0635 void Doctor::setOverscan(OutputPtr output, uint32_t overscan)
0636 {
0637     output->setOverscan(overscan);
0638     m_changed = true;
0639 }
0640 
0641 void Doctor::setVrrPolicy(OutputPtr output, KScreen::Output::VrrPolicy policy)
0642 {
0643     output->setVrrPolicy(policy);
0644     m_changed = true;
0645 }
0646 
0647 void Doctor::setRgbRange(OutputPtr output, KScreen::Output::RgbRange rgbRange)
0648 {
0649     output->setRgbRange(rgbRange);
0650     m_changed = true;
0651 }
0652 
0653 void KScreen::Doctor::setPrimary(OutputPtr output)
0654 {
0655     setPriority(output, 1);
0656 }
0657 
0658 void KScreen::Doctor::setPriority(OutputPtr output, uint32_t priority)
0659 {
0660     m_config->setOutputPriority(output, priority);
0661     m_changed = true;
0662 }
0663 
0664 void Doctor::setHdrEnabled(OutputPtr output, bool enable)
0665 {
0666     output->setHdrEnabled(enable);
0667     m_changed = true;
0668 }
0669 
0670 void Doctor::setSdrBrightness(OutputPtr output, uint32_t brightness)
0671 {
0672     output->setSdrBrightness(brightness);
0673     m_changed = true;
0674 }
0675 
0676 void Doctor::setWcgEnabled(OutputPtr output, bool enable)
0677 {
0678     output->setWcgEnabled(enable);
0679     m_changed = true;
0680 }
0681 
0682 void Doctor::applyConfig()
0683 {
0684     if (!m_changed) {
0685         return;
0686     }
0687     auto setop = new SetConfigOperation(m_config, this);
0688     setop->exec();
0689     qCDebug(KSCREEN_DOCTOR) << "setop exec returned" << m_config;
0690     qApp->exit(0);
0691 }
0692 
0693 #include "moc_doctor.cpp"