File indexing completed on 2024-11-10 04:55:32
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"