File indexing completed on 2024-05-19 09:26:44

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