File indexing completed on 2024-05-05 17:34:27

0001 /*
0002     SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
0003     SPDX-FileCopyrightText: 2021 Xaver Hugl <xaver.hugl@gmail.com>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 #include "control.h"
0008 #include "globals.h"
0009 
0010 #include <KDirWatch>
0011 #include <QDir>
0012 #include <QFile>
0013 #include <QJsonDocument>
0014 #include <QStringBuilder>
0015 
0016 #include <kscreen/config.h>
0017 
0018 // clang-format off
0019 #define retentionString                 QStringLiteral("retention")
0020 #define nameString                      QStringLiteral("name")
0021 #define scaleString                     QStringLiteral("scale")
0022 #define metadataString                  QStringLiteral("metadata")
0023 #define idString                        QStringLiteral("id")
0024 #define autorotateString                QStringLiteral("autorotate")
0025 #define autorotateTabletOnlyString      QStringLiteral("autorotate-tablet-only")
0026 #define replicateHashString             QStringLiteral("replicate-hash")
0027 #define replicateNameString             QStringLiteral("replicate-name")
0028 #define overscanString                  QStringLiteral("overscan")
0029 #define vrrPolicyString                 QStringLiteral("vrrpolicy")
0030 #define rgbRangeString                  QStringLiteral("rgbrange")
0031 #define outputsString                   QStringLiteral("outputs")
0032 // clang-format on
0033 
0034 QString Control::s_dirName = QStringLiteral("control/");
0035 
0036 Control::Control(QObject *parent)
0037     : QObject(parent)
0038 {
0039 }
0040 
0041 void Control::activateWatcher()
0042 {
0043     if (m_watcher) {
0044         return;
0045     }
0046     m_watcher = new KDirWatch(this);
0047     m_watcher->addFile(filePath());
0048     connect(m_watcher, &KDirWatch::dirty, this, [this]() {
0049         readFile();
0050         Q_EMIT changed();
0051     });
0052 }
0053 
0054 KDirWatch *Control::watcher() const
0055 {
0056     return m_watcher;
0057 }
0058 
0059 bool Control::writeFile()
0060 {
0061     const QString path = filePath();
0062     const auto infoMap = constInfo();
0063 
0064     if (infoMap.isEmpty()) {
0065         // Nothing to write. Default control. Remove file if it exists.
0066         QFile::remove(path);
0067         return true;
0068     }
0069     if (!QDir().mkpath(dirPath())) {
0070         // TODO: error message
0071         return false;
0072     }
0073 
0074     // write updated data to file
0075     QFile file(path);
0076     if (!file.open(QIODevice::WriteOnly)) {
0077         // TODO: logging category?
0078         //        qCWarning(KSCREEN_COMMON) << "Failed to open config control file for writing! " << file.errorString();
0079         return false;
0080     }
0081     file.write(QJsonDocument::fromVariant(infoMap).toJson());
0082     //    qCDebug(KSCREEN_COMMON) << "Control saved on: " << file.fileName();
0083     return true;
0084 }
0085 
0086 QString Control::dirPath() const
0087 {
0088     return Globals::dirPath() % s_dirName;
0089 }
0090 
0091 void Control::readFile()
0092 {
0093     QFile file(filePath());
0094     if (file.open(QIODevice::ReadOnly)) {
0095         // This might not be reached, bus this is ok. The control file will
0096         // eventually be created on first write later on.
0097         QJsonDocument parser;
0098         m_info = parser.fromJson(file.readAll()).toVariant().toMap();
0099     }
0100 }
0101 
0102 QString Control::filePathFromHash(const QString &hash) const
0103 {
0104     return dirPath() % hash;
0105 }
0106 
0107 QVariantMap &Control::info()
0108 {
0109     return m_info;
0110 }
0111 
0112 const QVariantMap &Control::constInfo() const
0113 {
0114     return m_info;
0115 }
0116 
0117 Control::OutputRetention Control::convertVariantToOutputRetention(QVariant variant)
0118 {
0119     if (variant.canConvert<int>()) {
0120         const auto retention = variant.toInt();
0121         if (retention == (int)OutputRetention::Global) {
0122             return OutputRetention::Global;
0123         }
0124         if (retention == (int)OutputRetention::Individual) {
0125             return OutputRetention::Individual;
0126         }
0127     }
0128     return OutputRetention::Undefined;
0129 }
0130 
0131 ControlConfig::ControlConfig(KScreen::ConfigPtr config, QObject *parent)
0132     : Control(parent)
0133     , m_config(config)
0134 {
0135     //    qDebug() << "Looking for control file:" << config->connectedOutputsHash();
0136     readFile();
0137 
0138     // TODO: use a file watcher in case of changes to the control file while
0139     //       object exists?
0140 
0141     // As global outputs are indexed by a hash of their edid, which is not unique,
0142     // to be able to tell apart multiple identical outputs, these need special treatment
0143     QStringList allIds;
0144     const auto outputs = config->outputs();
0145     allIds.reserve(outputs.count());
0146     for (const KScreen::OutputPtr &output : outputs) {
0147         const auto outputId = output->hashMd5();
0148         if (allIds.contains(outputId) && !m_duplicateOutputIds.contains(outputId)) {
0149             m_duplicateOutputIds << outputId;
0150         }
0151         allIds << outputId;
0152     }
0153 
0154     for (const auto &output : outputs) {
0155         m_outputsControls << new ControlOutput(output, this);
0156     }
0157 
0158     // TODO: this is same in Output::readInOutputs of the daemon. Combine?
0159 
0160     // TODO: connect to outputs added/removed signals and reevaluate duplicate ids
0161     //       in case of such a change while object exists?
0162 }
0163 
0164 void ControlConfig::activateWatcher()
0165 {
0166     if (watcher()) {
0167         // Watcher was already activated.
0168         return;
0169     }
0170     for (auto *output : qAsConst(m_outputsControls)) {
0171         output->activateWatcher();
0172         connect(output, &ControlOutput::changed, this, &ControlConfig::changed);
0173     }
0174 }
0175 
0176 QString ControlConfig::dirPath() const
0177 {
0178     return Control::dirPath() % QStringLiteral("configs/");
0179 }
0180 
0181 QString ControlConfig::filePath() const
0182 {
0183     if (!m_config) {
0184         return QString();
0185     }
0186     return filePathFromHash(m_config->connectedOutputsHash());
0187 }
0188 
0189 bool ControlConfig::writeFile()
0190 {
0191     bool success = true;
0192     for (auto *outputControl : qAsConst(m_outputsControls)) {
0193         if (getOutputRetention(outputControl->id(), outputControl->name()) == OutputRetention::Individual) {
0194             continue;
0195         }
0196         success &= outputControl->writeFile();
0197     }
0198     return success && Control::writeFile();
0199 }
0200 
0201 bool ControlConfig::infoIsOutput(const QVariantMap &info, const QString &outputId, const QString &outputName) const
0202 {
0203     const QString outputIdInfo = info[idString].toString();
0204     if (outputIdInfo.isEmpty()) {
0205         return false;
0206     }
0207     if (outputId != outputIdInfo) {
0208         return false;
0209     }
0210 
0211     if (!outputName.isEmpty() && m_duplicateOutputIds.contains(outputId)) {
0212         // We may have identical outputs connected, these will have the same id in the config
0213         // in order to find the right one, also check the output's name (usually the connector)
0214         const auto metadata = info[metadataString].toMap();
0215         const auto outputNameInfo = metadata[nameString].toString();
0216         if (outputName != outputNameInfo) {
0217             // was a duplicate id, but info not for this output
0218             return false;
0219         }
0220     }
0221     return true;
0222 }
0223 
0224 Control::OutputRetention ControlConfig::getOutputRetention(const KScreen::OutputPtr &output) const
0225 {
0226     return getOutputRetention(output->hashMd5(), output->name());
0227 }
0228 
0229 Control::OutputRetention ControlConfig::getOutputRetention(const QString &outputId, const QString &outputName) const
0230 {
0231     const QVariantList outputsInfo = getOutputs();
0232     for (const auto &variantInfo : outputsInfo) {
0233         const QVariantMap info = variantInfo.toMap();
0234         if (!infoIsOutput(info, outputId, outputName)) {
0235             continue;
0236         }
0237         return convertVariantToOutputRetention(info[retentionString]);
0238     }
0239     // info for output not found
0240     return OutputRetention::Undefined;
0241 }
0242 
0243 static QVariantMap metadata(const QString &outputName)
0244 {
0245     QVariantMap metadata;
0246     metadata[nameString] = outputName;
0247     return metadata;
0248 }
0249 
0250 QVariantMap createOutputInfo(const QString &outputId, const QString &outputName)
0251 {
0252     QVariantMap outputInfo;
0253     outputInfo[idString] = outputId;
0254     outputInfo[metadataString] = metadata(outputName);
0255     return outputInfo;
0256 }
0257 
0258 void ControlConfig::setOutputRetention(const KScreen::OutputPtr &output, OutputRetention value)
0259 {
0260     setOutputRetention(output->hashMd5(), output->name(), value);
0261 }
0262 
0263 void ControlConfig::setOutputRetention(const QString &outputId, const QString &outputName, OutputRetention value)
0264 {
0265     QList<QVariant>::iterator it;
0266     QVariantList outputsInfo = getOutputs();
0267 
0268     for (it = outputsInfo.begin(); it != outputsInfo.end(); ++it) {
0269         QVariantMap outputInfo = (*it).toMap();
0270         if (!infoIsOutput(outputInfo, outputId, outputName)) {
0271             continue;
0272         }
0273         outputInfo[retentionString] = (int)value;
0274         *it = outputInfo;
0275         setOutputs(outputsInfo);
0276         return;
0277     }
0278     // no entry yet, create one
0279     auto outputInfo = createOutputInfo(outputId, outputName);
0280     outputInfo[retentionString] = (int)value;
0281 
0282     outputsInfo << outputInfo;
0283     setOutputs(outputsInfo);
0284 }
0285 
0286 template<typename T, typename F>
0287 T ControlConfig::get(const KScreen::OutputPtr &output, const QString &name, F globalRetentionFunc, T defaultValue) const
0288 {
0289     const auto &outputId = output->hashMd5();
0290     const auto &outputName = output->name();
0291     const auto retention = getOutputRetention(outputId, outputName);
0292     if (retention == OutputRetention::Individual) {
0293         const QVariantList outputsInfo = getOutputs();
0294         for (const auto &variantInfo : outputsInfo) {
0295             const QVariantMap info = variantInfo.toMap();
0296             if (!infoIsOutput(info, outputId, outputName)) {
0297                 continue;
0298             }
0299             const auto val = info[name];
0300             if (val.template canConvert<T>()) {
0301                 return val.template value<T>();
0302             } else {
0303                 return defaultValue;
0304             }
0305         }
0306     }
0307     // Retention is global or info for output not in config control file.
0308     if (auto *outputControl = getOutputControl(outputId, outputName)) {
0309         return (outputControl->*globalRetentionFunc)();
0310     }
0311 
0312     // Info for output not found.
0313     return defaultValue;
0314 }
0315 
0316 template<typename T, typename F, typename V>
0317 void ControlConfig::set(const KScreen::OutputPtr &output, const QString &name, F globalRetentionFunc, V value)
0318 {
0319     const auto &outputId = output->hashMd5();
0320     const auto &outputName = output->name();
0321     QList<QVariant>::iterator it;
0322     QVariantList outputsInfo = getOutputs();
0323 
0324     for (it = outputsInfo.begin(); it != outputsInfo.end(); ++it) {
0325         QVariantMap outputInfo = (*it).toMap();
0326         if (!infoIsOutput(outputInfo, outputId, outputName)) {
0327             continue;
0328         }
0329         outputInfo[name] = static_cast<T>(value);
0330         *it = outputInfo;
0331         setOutputs(outputsInfo);
0332         if (auto *control = getOutputControl(outputId, outputName)) {
0333             (control->*globalRetentionFunc)(value);
0334         }
0335         return;
0336     }
0337     // no entry yet, create one
0338     auto outputInfo = createOutputInfo(outputId, outputName);
0339     outputInfo[name] = static_cast<T>(value);
0340 
0341     outputsInfo << outputInfo;
0342     setOutputs(outputsInfo);
0343     if (auto *control = getOutputControl(outputId, outputName)) {
0344         (control->*globalRetentionFunc)(value);
0345     }
0346 }
0347 
0348 bool ControlConfig::getAutoRotate(const KScreen::OutputPtr &output) const
0349 {
0350     return get(output, autorotateString, &ControlOutput::getAutoRotate, true);
0351 }
0352 
0353 void ControlConfig::setAutoRotate(const KScreen::OutputPtr &output, bool value)
0354 {
0355     set<bool>(output, autorotateString, &ControlOutput::setAutoRotate, value);
0356 }
0357 
0358 bool ControlConfig::getAutoRotateOnlyInTabletMode(const KScreen::OutputPtr &output) const
0359 {
0360     return get(output, autorotateTabletOnlyString, &ControlOutput::getAutoRotateOnlyInTabletMode, true);
0361 }
0362 
0363 void ControlConfig::setAutoRotateOnlyInTabletMode(const KScreen::OutputPtr &output, bool value)
0364 {
0365     set<bool>(output, autorotateTabletOnlyString, &ControlOutput::setAutoRotateOnlyInTabletMode, value);
0366 }
0367 
0368 KScreen::OutputPtr ControlConfig::getReplicationSource(const KScreen::OutputPtr &output) const
0369 {
0370     const QVariantList outputsInfo = getOutputs();
0371     for (const auto &variantInfo : outputsInfo) {
0372         const QVariantMap info = variantInfo.toMap();
0373         if (!infoIsOutput(info, output->hashMd5(), output->name())) {
0374             continue;
0375         }
0376         const QString sourceHash = info[replicateHashString].toString();
0377         const QString sourceName = info[replicateNameString].toString();
0378 
0379         if (sourceHash.isEmpty() && sourceName.isEmpty()) {
0380             // Common case when the replication source has been unset.
0381             return nullptr;
0382         }
0383 
0384         const auto outputs = m_config->outputs();
0385         for (const auto &output : outputs) {
0386             if (output->hashMd5() == sourceHash && output->name() == sourceName) {
0387                 return output;
0388             }
0389         }
0390         // No match.
0391         return nullptr;
0392     }
0393     // Info for output not found.
0394     return nullptr;
0395 }
0396 
0397 void ControlConfig::setReplicationSource(const KScreen::OutputPtr &output, const KScreen::OutputPtr &source)
0398 {
0399     QList<QVariant>::iterator it;
0400     QVariantList outputsInfo = getOutputs();
0401     const QString sourceHash = source ? source->hashMd5() : QString();
0402     const QString sourceName = source ? source->name() : QString();
0403 
0404     for (it = outputsInfo.begin(); it != outputsInfo.end(); ++it) {
0405         QVariantMap outputInfo = (*it).toMap();
0406         if (!infoIsOutput(outputInfo, output->hashMd5(), output->name())) {
0407             continue;
0408         }
0409         outputInfo[replicateHashString] = sourceHash;
0410         outputInfo[replicateNameString] = sourceName;
0411         *it = outputInfo;
0412         setOutputs(outputsInfo);
0413         // TODO: shall we set this information also as new global value (like with auto-rotate)?
0414         return;
0415     }
0416     // no entry yet, create one
0417     auto outputInfo = createOutputInfo(output->hashMd5(), output->name());
0418     outputInfo[replicateHashString] = sourceHash;
0419     outputInfo[replicateNameString] = sourceName;
0420 
0421     outputsInfo << outputInfo;
0422     setOutputs(outputsInfo);
0423     // TODO: shall we set this information also as new global value (like with auto-rotate)?
0424 }
0425 
0426 uint32_t ControlConfig::getOverscan(const KScreen::OutputPtr &output) const
0427 {
0428     return get(output, overscanString, &ControlOutput::overscan, 0);
0429 }
0430 
0431 void ControlConfig::setOverscan(const KScreen::OutputPtr &output, const uint32_t value)
0432 {
0433     set<uint32_t>(output, overscanString, &ControlOutput::setOverscan, value);
0434 }
0435 
0436 KScreen::Output::VrrPolicy ControlConfig::getVrrPolicy(const KScreen::OutputPtr &output) const
0437 {
0438     return get(output, vrrPolicyString, &ControlOutput::vrrPolicy, KScreen::Output::VrrPolicy::Automatic);
0439 }
0440 
0441 void ControlConfig::setVrrPolicy(const KScreen::OutputPtr &output, const KScreen::Output::VrrPolicy value)
0442 {
0443     set<uint32_t>(output, vrrPolicyString, &ControlOutput::setVrrPolicy, value);
0444 }
0445 
0446 KScreen::Output::RgbRange ControlConfig::getRgbRange(const KScreen::OutputPtr &output) const
0447 {
0448     return get(output, rgbRangeString, &ControlOutput::rgbRange, KScreen::Output::RgbRange::Automatic);
0449 }
0450 
0451 void ControlConfig::setRgbRange(const KScreen::OutputPtr &output, const KScreen::Output::RgbRange value)
0452 {
0453     set<uint32_t>(output, rgbRangeString, &ControlOutput::setRgbRange, value);
0454 }
0455 
0456 QVariantList ControlConfig::getOutputs() const
0457 {
0458     return constInfo()[outputsString].toList();
0459 }
0460 
0461 void ControlConfig::setOutputs(QVariantList outputsInfo)
0462 {
0463     auto &infoMap = info();
0464     infoMap[outputsString] = outputsInfo;
0465 }
0466 
0467 ControlOutput *ControlConfig::getOutputControl(const QString &outputId, const QString &outputName) const
0468 {
0469     for (auto *control : m_outputsControls) {
0470         if (control->id() == outputId && control->name() == outputName) {
0471             return control;
0472         }
0473     }
0474     return nullptr;
0475 }
0476 
0477 ControlOutput::ControlOutput(KScreen::OutputPtr output, QObject *parent)
0478     : Control(parent)
0479     , m_output(output)
0480 {
0481     readFile();
0482 }
0483 
0484 QString ControlOutput::id() const
0485 {
0486     return m_output->hashMd5();
0487 }
0488 
0489 QString ControlOutput::name() const
0490 {
0491     return m_output->name();
0492 }
0493 
0494 QString ControlOutput::dirPath() const
0495 {
0496     return Control::dirPath() % QStringLiteral("outputs/");
0497 }
0498 
0499 QString ControlOutput::filePath() const
0500 {
0501     if (!m_output) {
0502         return QString();
0503     }
0504     return filePathFromHash(m_output->hashMd5());
0505 }
0506 
0507 bool ControlOutput::getAutoRotate() const
0508 {
0509     const auto val = constInfo()[autorotateString];
0510     return !val.canConvert<bool>() || val.toBool();
0511 }
0512 
0513 void ControlOutput::setAutoRotate(bool value)
0514 {
0515     auto &infoMap = info();
0516     if (infoMap.isEmpty()) {
0517         infoMap = createOutputInfo(m_output->hashMd5(), m_output->name());
0518     }
0519     infoMap[autorotateString] = value;
0520 }
0521 
0522 bool ControlOutput::getAutoRotateOnlyInTabletMode() const
0523 {
0524     const auto val = constInfo()[autorotateTabletOnlyString];
0525     return !val.canConvert<bool>() || val.toBool();
0526 }
0527 
0528 void ControlOutput::setAutoRotateOnlyInTabletMode(bool value)
0529 {
0530     auto &infoMap = info();
0531     if (infoMap.isEmpty()) {
0532         infoMap = createOutputInfo(m_output->hashMd5(), m_output->name());
0533     }
0534     infoMap[autorotateTabletOnlyString] = value;
0535 }
0536 
0537 uint32_t ControlOutput::overscan() const
0538 {
0539     const auto val = constInfo()[overscanString];
0540     if (val.canConvert<uint>()) {
0541         return val.toUInt();
0542     }
0543     return 0;
0544 }
0545 
0546 void ControlOutput::setOverscan(uint32_t value)
0547 {
0548     auto &infoMap = info();
0549     if (infoMap.isEmpty()) {
0550         infoMap = createOutputInfo(m_output->hashMd5(), m_output->name());
0551     }
0552     infoMap[overscanString] = static_cast<uint>(value);
0553 }
0554 
0555 KScreen::Output::VrrPolicy ControlOutput::vrrPolicy() const
0556 {
0557     const auto val = constInfo()[vrrPolicyString];
0558     if (val.canConvert<uint>()) {
0559         return static_cast<KScreen::Output::VrrPolicy>(val.toUInt());
0560     }
0561     return KScreen::Output::VrrPolicy::Automatic;
0562 }
0563 
0564 void ControlOutput::setVrrPolicy(KScreen::Output::VrrPolicy value)
0565 {
0566     auto &infoMap = info();
0567     if (infoMap.isEmpty()) {
0568         infoMap = createOutputInfo(m_output->hashMd5(), m_output->name());
0569     }
0570     infoMap[vrrPolicyString] = static_cast<uint>(value);
0571 }
0572 
0573 KScreen::Output::RgbRange ControlOutput::rgbRange() const
0574 {
0575     const auto val = constInfo()[rgbRangeString];
0576     if (val.canConvert<uint>()) {
0577         return static_cast<KScreen::Output::RgbRange>(val.toUInt());
0578     }
0579     return KScreen::Output::RgbRange::Automatic;
0580 }
0581 
0582 void ControlOutput::setRgbRange(KScreen::Output::RgbRange value)
0583 {
0584     auto &infoMap = info();
0585     if (infoMap.isEmpty()) {
0586         infoMap = createOutputInfo(m_output->hashMd5(), m_output->name());
0587     }
0588     infoMap[rgbRangeString] = static_cast<uint>(value);
0589 }