File indexing completed on 2024-04-28 05:27:37

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 nameString                      QStringLiteral("name")
0020 #define scaleString                     QStringLiteral("scale")
0021 #define metadataString                  QStringLiteral("metadata")
0022 #define idString                        QStringLiteral("id")
0023 #define autorotateString                QStringLiteral("autorotate")
0024 #define autorotateTabletOnlyString      QStringLiteral("autorotate-tablet-only")
0025 #define replicateHashString             QStringLiteral("replicate-hash")
0026 #define replicateNameString             QStringLiteral("replicate-name")
0027 #define overscanString                  QStringLiteral("overscan")
0028 #define vrrPolicyString                 QStringLiteral("vrrpolicy")
0029 #define rgbRangeString                  QStringLiteral("rgbrange")
0030 #define outputsString                   QStringLiteral("outputs")
0031 // clang-format on
0032 
0033 QString Control::s_dirName = QStringLiteral("control/");
0034 
0035 Control::Control(QObject *parent)
0036     : QObject(parent)
0037 {
0038 }
0039 
0040 void Control::activateWatcher()
0041 {
0042     if (m_watcher) {
0043         return;
0044     }
0045     m_watcher = new KDirWatch(this);
0046     m_watcher->addFile(filePath());
0047     connect(m_watcher, &KDirWatch::dirty, this, [this]() {
0048         readFile();
0049         Q_EMIT changed();
0050     });
0051 }
0052 
0053 KDirWatch *Control::watcher() const
0054 {
0055     return m_watcher;
0056 }
0057 
0058 bool Control::writeFile()
0059 {
0060     const QString path = filePath();
0061     const auto infoMap = constInfo();
0062 
0063     if (infoMap.isEmpty()) {
0064         // Nothing to write. Default control. Remove file if it exists.
0065         QFile::remove(path);
0066         return true;
0067     }
0068     if (!QDir().mkpath(dirPath())) {
0069         // TODO: error message
0070         return false;
0071     }
0072 
0073     // write updated data to file
0074     QFile file(path);
0075     if (!file.open(QIODevice::WriteOnly)) {
0076         // TODO: logging category?
0077         //        qCWarning(KSCREEN_COMMON) << "Failed to open config control file for writing! " << file.errorString();
0078         return false;
0079     }
0080     file.write(QJsonDocument::fromVariant(infoMap).toJson());
0081     //    qCDebug(KSCREEN_COMMON) << "Control saved on: " << file.fileName();
0082     return true;
0083 }
0084 
0085 QString Control::dirPath() const
0086 {
0087     return Globals::dirPath() % s_dirName;
0088 }
0089 
0090 void Control::readFile()
0091 {
0092     QFile file(filePath());
0093     if (file.open(QIODevice::ReadOnly)) {
0094         // This might not be reached, bus this is ok. The control file will
0095         // eventually be created on first write later on.
0096         QJsonDocument parser;
0097         m_info = parser.fromJson(file.readAll()).toVariant().toMap();
0098     }
0099 }
0100 
0101 QString Control::filePathFromHash(const QString &hash) const
0102 {
0103     return dirPath() % hash;
0104 }
0105 
0106 QVariantMap &Control::info()
0107 {
0108     return m_info;
0109 }
0110 
0111 const QVariantMap &Control::constInfo() const
0112 {
0113     return m_info;
0114 }
0115 
0116 ControlConfig::ControlConfig(KScreen::ConfigPtr config, QObject *parent)
0117     : Control(parent)
0118     , m_config(config)
0119 {
0120     //    qDebug() << "Looking for control file:" << config->connectedOutputsHash();
0121     readFile();
0122 
0123     // TODO: use a file watcher in case of changes to the control file while
0124     //       object exists?
0125 
0126     // As global outputs are indexed by a hash of their edid, which is not unique,
0127     // to be able to tell apart multiple identical outputs, these need special treatment
0128     QStringList allIds;
0129     const auto outputs = config->outputs();
0130     allIds.reserve(outputs.count());
0131     for (const KScreen::OutputPtr &output : outputs) {
0132         const auto outputId = output->hashMd5();
0133         if (allIds.contains(outputId) && !m_duplicateOutputIds.contains(outputId)) {
0134             m_duplicateOutputIds << outputId;
0135         }
0136         allIds << outputId;
0137     }
0138 
0139     for (const auto &output : outputs) {
0140         m_outputsControls << new ControlOutput(output, this);
0141     }
0142 
0143     // TODO: this is same in Output::readInOutputs of the daemon. Combine?
0144 
0145     // TODO: connect to outputs added/removed signals and reevaluate duplicate ids
0146     //       in case of such a change while object exists?
0147 }
0148 
0149 void ControlConfig::activateWatcher()
0150 {
0151     if (watcher()) {
0152         // Watcher was already activated.
0153         return;
0154     }
0155     for (auto *output : std::as_const(m_outputsControls)) {
0156         output->activateWatcher();
0157         connect(output, &ControlOutput::changed, this, &ControlConfig::changed);
0158     }
0159 }
0160 
0161 QString ControlConfig::dirPath() const
0162 {
0163     return Control::dirPath() % QStringLiteral("configs/");
0164 }
0165 
0166 QString ControlConfig::filePath() const
0167 {
0168     if (!m_config) {
0169         return QString();
0170     }
0171     return filePathFromHash(m_config->connectedOutputsHash());
0172 }
0173 
0174 bool ControlConfig::writeFile()
0175 {
0176     bool success = true;
0177     for (auto *outputControl : std::as_const(m_outputsControls)) {
0178         success &= outputControl->writeFile();
0179     }
0180     return success && Control::writeFile();
0181 }
0182 
0183 bool ControlConfig::infoIsOutput(const QVariantMap &info, const QString &outputId, const QString &outputName) const
0184 {
0185     const QString outputIdInfo = info[idString].toString();
0186     if (outputIdInfo.isEmpty()) {
0187         return false;
0188     }
0189     if (outputId != outputIdInfo) {
0190         return false;
0191     }
0192 
0193     if (!outputName.isEmpty() && m_duplicateOutputIds.contains(outputId)) {
0194         // We may have identical outputs connected, these will have the same id in the config
0195         // in order to find the right one, also check the output's name (usually the connector)
0196         const auto metadata = info[metadataString].toMap();
0197         const auto outputNameInfo = metadata[nameString].toString();
0198         if (outputName != outputNameInfo) {
0199             // was a duplicate id, but info not for this output
0200             return false;
0201         }
0202     }
0203     return true;
0204 }
0205 
0206 static QVariantMap metadata(const QString &outputName)
0207 {
0208     QVariantMap metadata;
0209     metadata[nameString] = outputName;
0210     return metadata;
0211 }
0212 
0213 QVariantMap createOutputInfo(const QString &outputId, const QString &outputName)
0214 {
0215     QVariantMap outputInfo;
0216     outputInfo[idString] = outputId;
0217     outputInfo[metadataString] = metadata(outputName);
0218     return outputInfo;
0219 }
0220 
0221 template<typename T, typename F>
0222 T ControlConfig::get(const KScreen::OutputPtr &output, F globalRetentionFunc, T defaultValue) const
0223 {
0224     if (auto *outputControl = getOutputControl(output->hashMd5(), output->name())) {
0225         return (outputControl->*globalRetentionFunc)();
0226     } else {
0227         return defaultValue;
0228     }
0229 }
0230 
0231 template<typename T, typename F, typename V>
0232 void ControlConfig::set(const KScreen::OutputPtr &output, const QString &name, F globalRetentionFunc, V value)
0233 {
0234     const auto &outputId = output->hashMd5();
0235     const auto &outputName = output->name();
0236     QList<QVariant>::iterator it;
0237     QVariantList outputsInfo = getOutputs();
0238 
0239     for (it = outputsInfo.begin(); it != outputsInfo.end(); ++it) {
0240         QVariantMap outputInfo = (*it).toMap();
0241         if (!infoIsOutput(outputInfo, outputId, outputName)) {
0242             continue;
0243         }
0244         outputInfo[name] = static_cast<T>(value);
0245         *it = outputInfo;
0246         setOutputs(outputsInfo);
0247         if (auto *control = getOutputControl(outputId, outputName)) {
0248             (control->*globalRetentionFunc)(value);
0249         }
0250         return;
0251     }
0252     // no entry yet, create one
0253     auto outputInfo = createOutputInfo(outputId, outputName);
0254     outputInfo[name] = static_cast<T>(value);
0255 
0256     outputsInfo << outputInfo;
0257     setOutputs(outputsInfo);
0258     if (auto *control = getOutputControl(outputId, outputName)) {
0259         (control->*globalRetentionFunc)(value);
0260     }
0261 }
0262 
0263 KScreen::OutputPtr ControlConfig::getReplicationSource(const KScreen::OutputPtr &output) const
0264 {
0265     const QVariantList outputsInfo = getOutputs();
0266     for (const auto &variantInfo : outputsInfo) {
0267         const QVariantMap info = variantInfo.toMap();
0268         if (!infoIsOutput(info, output->hashMd5(), output->name())) {
0269             continue;
0270         }
0271         const QString sourceHash = info[replicateHashString].toString();
0272         const QString sourceName = info[replicateNameString].toString();
0273 
0274         if (sourceHash.isEmpty() && sourceName.isEmpty()) {
0275             // Common case when the replication source has been unset.
0276             return nullptr;
0277         }
0278 
0279         const auto outputs = m_config->outputs();
0280         for (const auto &output : outputs) {
0281             if (output->hashMd5() == sourceHash && output->name() == sourceName) {
0282                 return output;
0283             }
0284         }
0285         // No match.
0286         return nullptr;
0287     }
0288     // Info for output not found.
0289     return nullptr;
0290 }
0291 
0292 void ControlConfig::setReplicationSource(const KScreen::OutputPtr &output, const KScreen::OutputPtr &source)
0293 {
0294     QList<QVariant>::iterator it;
0295     QVariantList outputsInfo = getOutputs();
0296     const QString sourceHash = source ? source->hashMd5() : QString();
0297     const QString sourceName = source ? source->name() : QString();
0298 
0299     for (it = outputsInfo.begin(); it != outputsInfo.end(); ++it) {
0300         QVariantMap outputInfo = (*it).toMap();
0301         if (!infoIsOutput(outputInfo, output->hashMd5(), output->name())) {
0302             continue;
0303         }
0304         outputInfo[replicateHashString] = sourceHash;
0305         outputInfo[replicateNameString] = sourceName;
0306         *it = outputInfo;
0307         setOutputs(outputsInfo);
0308         // TODO: shall we set this information also as new global value (like with auto-rotate)?
0309         return;
0310     }
0311     // no entry yet, create one
0312     auto outputInfo = createOutputInfo(output->hashMd5(), output->name());
0313     outputInfo[replicateHashString] = sourceHash;
0314     outputInfo[replicateNameString] = sourceName;
0315 
0316     outputsInfo << outputInfo;
0317     setOutputs(outputsInfo);
0318     // TODO: shall we set this information also as new global value (like with auto-rotate)?
0319 }
0320 
0321 uint32_t ControlConfig::getOverscan(const KScreen::OutputPtr &output) const
0322 {
0323     return get(output, &ControlOutput::overscan, 0);
0324 }
0325 
0326 void ControlConfig::setOverscan(const KScreen::OutputPtr &output, const uint32_t value)
0327 {
0328     set<uint32_t>(output, overscanString, &ControlOutput::setOverscan, value);
0329 }
0330 
0331 KScreen::Output::VrrPolicy ControlConfig::getVrrPolicy(const KScreen::OutputPtr &output) const
0332 {
0333     return get(output, &ControlOutput::vrrPolicy, KScreen::Output::VrrPolicy::Automatic);
0334 }
0335 
0336 void ControlConfig::setVrrPolicy(const KScreen::OutputPtr &output, const KScreen::Output::VrrPolicy value)
0337 {
0338     set<uint32_t>(output, vrrPolicyString, &ControlOutput::setVrrPolicy, value);
0339 }
0340 
0341 KScreen::Output::RgbRange ControlConfig::getRgbRange(const KScreen::OutputPtr &output) const
0342 {
0343     return get(output, &ControlOutput::rgbRange, KScreen::Output::RgbRange::Automatic);
0344 }
0345 
0346 void ControlConfig::setRgbRange(const KScreen::OutputPtr &output, const KScreen::Output::RgbRange value)
0347 {
0348     set<uint32_t>(output, rgbRangeString, &ControlOutput::setRgbRange, value);
0349 }
0350 
0351 QVariantList ControlConfig::getOutputs() const
0352 {
0353     return constInfo()[outputsString].toList();
0354 }
0355 
0356 void ControlConfig::setOutputs(QVariantList outputsInfo)
0357 {
0358     auto &infoMap = info();
0359     infoMap[outputsString] = outputsInfo;
0360 }
0361 
0362 ControlOutput *ControlConfig::getOutputControl(const QString &outputId, const QString &outputName) const
0363 {
0364     for (auto *control : m_outputsControls) {
0365         if (control->id() == outputId && control->name() == outputName) {
0366             return control;
0367         }
0368     }
0369     return nullptr;
0370 }
0371 
0372 ControlOutput::ControlOutput(KScreen::OutputPtr output, QObject *parent)
0373     : Control(parent)
0374     , m_output(output)
0375 {
0376     readFile();
0377 }
0378 
0379 QString ControlOutput::id() const
0380 {
0381     return m_output->hashMd5();
0382 }
0383 
0384 QString ControlOutput::name() const
0385 {
0386     return m_output->name();
0387 }
0388 
0389 QString ControlOutput::dirPath() const
0390 {
0391     return Control::dirPath() % QStringLiteral("outputs/");
0392 }
0393 
0394 QString ControlOutput::filePath() const
0395 {
0396     if (!m_output) {
0397         return QString();
0398     }
0399     return filePathFromHash(m_output->hashMd5());
0400 }
0401 
0402 uint32_t ControlOutput::overscan() const
0403 {
0404     const auto val = constInfo()[overscanString];
0405     if (val.canConvert<uint>()) {
0406         return val.toUInt();
0407     }
0408     return 0;
0409 }
0410 
0411 void ControlOutput::setOverscan(uint32_t value)
0412 {
0413     auto &infoMap = info();
0414     if (infoMap.isEmpty()) {
0415         infoMap = createOutputInfo(m_output->hashMd5(), m_output->name());
0416     }
0417     infoMap[overscanString] = static_cast<uint>(value);
0418 }
0419 
0420 KScreen::Output::VrrPolicy ControlOutput::vrrPolicy() const
0421 {
0422     const auto val = constInfo()[vrrPolicyString];
0423     if (val.canConvert<uint>()) {
0424         return static_cast<KScreen::Output::VrrPolicy>(val.toUInt());
0425     }
0426     return KScreen::Output::VrrPolicy::Automatic;
0427 }
0428 
0429 void ControlOutput::setVrrPolicy(KScreen::Output::VrrPolicy value)
0430 {
0431     auto &infoMap = info();
0432     if (infoMap.isEmpty()) {
0433         infoMap = createOutputInfo(m_output->hashMd5(), m_output->name());
0434     }
0435     infoMap[vrrPolicyString] = static_cast<uint>(value);
0436 }
0437 
0438 KScreen::Output::RgbRange ControlOutput::rgbRange() const
0439 {
0440     const auto val = constInfo()[rgbRangeString];
0441     if (val.canConvert<uint>()) {
0442         return static_cast<KScreen::Output::RgbRange>(val.toUInt());
0443     }
0444     return KScreen::Output::RgbRange::Automatic;
0445 }
0446 
0447 void ControlOutput::setRgbRange(KScreen::Output::RgbRange value)
0448 {
0449     auto &infoMap = info();
0450     if (infoMap.isEmpty()) {
0451         infoMap = createOutputInfo(m_output->hashMd5(), m_output->name());
0452     }
0453     infoMap[rgbRangeString] = static_cast<uint>(value);
0454 }
0455 
0456 #include "moc_control.cpp"