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 }