File indexing completed on 2024-04-21 16:17:33
0001 /* 0002 * SPDX-FileCopyrightText: 2012 Alejandro Fiestas Olivares <afiestas@kde.org> 0003 * SPDX-FileCopyrightText: 2014 Daniel Vrátil <dvratil@redhat.com> 0004 * 0005 * SPDX-License-Identifier: LGPL-2.1-or-later 0006 */ 0007 0008 #include "config.h" 0009 0010 #include "backendmanager_p.h" 0011 #include "kscreen_debug.h" 0012 #include "mode.h" 0013 0014 #include <QCryptographicHash> 0015 #include <QDebug> 0016 #include <QRect> 0017 #include <QStringList> 0018 0019 #include <algorithm> 0020 #include <utility> 0021 0022 using namespace KScreen; 0023 0024 class Q_DECL_HIDDEN Config::Private : public QObject 0025 { 0026 Q_OBJECT 0027 public: 0028 Private(Config *parent) 0029 : QObject(parent) 0030 , valid(true) 0031 , supportedFeatures(Config::Feature::None) 0032 , tabletModeAvailable(false) 0033 , tabletModeEngaged(false) 0034 , q(parent) 0035 { 0036 } 0037 0038 KScreen::OutputPtr findPrimaryOutput() const 0039 { 0040 auto iter = std::find_if(outputs.constBegin(), outputs.constEnd(), [](const KScreen::OutputPtr &output) -> bool { 0041 return output->isPrimary(); 0042 }); 0043 return iter == outputs.constEnd() ? KScreen::OutputPtr() : iter.value(); 0044 } 0045 0046 // output priorities may be inconsistent after this call 0047 OutputList::Iterator removeOutput(OutputList::Iterator iter) 0048 { 0049 if (iter == outputs.end()) { 0050 return iter; 0051 } 0052 0053 const int outputId = iter.key(); 0054 OutputPtr output = iter.value(); 0055 0056 iter = outputs.erase(iter); 0057 0058 if (output) { 0059 output->disconnect(q); 0060 Q_EMIT q->outputRemoved(outputId); 0061 } 0062 0063 return iter; 0064 } 0065 0066 bool valid; 0067 ScreenPtr screen; 0068 OutputList outputs; 0069 Features supportedFeatures; 0070 bool tabletModeAvailable; 0071 bool tabletModeEngaged; 0072 0073 private: 0074 Config *q; 0075 }; 0076 0077 bool Config::canBeApplied(const ConfigPtr &config) 0078 { 0079 return canBeApplied(config, ValidityFlag::None); 0080 } 0081 0082 bool Config::canBeApplied(const ConfigPtr &config, ValidityFlags flags) 0083 { 0084 if (!config) { 0085 qCDebug(KSCREEN) << "canBeApplied: Config not available, returning false"; 0086 return false; 0087 } 0088 ConfigPtr currentConfig = BackendManager::instance()->config(); 0089 if (!currentConfig) { 0090 qCDebug(KSCREEN) << "canBeApplied: Current config not available, returning false"; 0091 return false; 0092 } 0093 0094 QRect rect; 0095 OutputPtr currentOutput; 0096 const OutputList outputs = config->outputs(); 0097 int enabledOutputsCount = 0; 0098 for (const OutputPtr &output : outputs) { 0099 if (!output->isEnabled()) { 0100 continue; 0101 } 0102 0103 ++enabledOutputsCount; 0104 0105 currentOutput = currentConfig->output(output->id()); 0106 // If there is no such output 0107 if (!currentOutput) { 0108 qCDebug(KSCREEN) << "canBeApplied: The output:" << output->id() << "does not exists"; 0109 return false; 0110 } 0111 // If the output is not connected 0112 if (!currentOutput->isConnected()) { 0113 qCDebug(KSCREEN) << "canBeApplied: The output:" << output->id() << "is not connected"; 0114 return false; 0115 } 0116 // if there is no currentMode 0117 if (output->currentModeId().isEmpty()) { 0118 qCDebug(KSCREEN) << "canBeApplied: The output:" << output->id() << "has no currentModeId"; 0119 return false; 0120 } 0121 // If the mode is not found in the current output 0122 if (!currentOutput->mode(output->currentModeId())) { 0123 qCDebug(KSCREEN) << "canBeApplied: The output:" << output->id() << "has no mode:" << output->currentModeId(); 0124 return false; 0125 } 0126 0127 const ModePtr currentMode = output->currentMode(); 0128 0129 const QSize outputSize = currentMode->size(); 0130 0131 if (output->pos().x() < rect.x()) { 0132 rect.setX(output->pos().x()); 0133 } 0134 0135 if (output->pos().y() < rect.y()) { 0136 rect.setY(output->pos().y()); 0137 } 0138 0139 QPoint bottomRight; 0140 if (output->isHorizontal()) { 0141 bottomRight = QPoint(output->pos().x() + outputSize.width(), output->pos().y() + outputSize.height()); 0142 } else { 0143 bottomRight = QPoint(output->pos().x() + outputSize.height(), output->pos().y() + outputSize.width()); 0144 } 0145 0146 if (bottomRight.x() > rect.width()) { 0147 rect.setWidth(bottomRight.x()); 0148 } 0149 0150 if (bottomRight.y() > rect.height()) { 0151 rect.setHeight(bottomRight.y()); 0152 } 0153 } 0154 0155 if (flags & ValidityFlag::RequireAtLeastOneEnabledScreen && enabledOutputsCount == 0) { 0156 qCDebug(KSCREEN) << "canBeAppled: There are no enabled screens, at least one required"; 0157 return false; 0158 } 0159 0160 const int maxEnabledOutputsCount = config->screen()->maxActiveOutputsCount(); 0161 if (enabledOutputsCount > maxEnabledOutputsCount) { 0162 qCDebug(KSCREEN).nospace() << "canBeApplied: Too many active screens. Requested: " << enabledOutputsCount << ", Max: " << maxEnabledOutputsCount; 0163 return false; 0164 } 0165 0166 if (rect.width() > config->screen()->maxSize().width()) { 0167 qCDebug(KSCREEN).nospace() << "canBeApplied: The configuration is too wide: " << rect.width() << ", Max: " << config->screen()->maxSize().width(); 0168 return false; 0169 } 0170 if (rect.height() > config->screen()->maxSize().height()) { 0171 qCDebug(KSCREEN).nospace() << "canBeApplied: The configuration is too high: " << rect.height() << ", Max: " << config->screen()->maxSize().height(); 0172 return false; 0173 } 0174 0175 return true; 0176 } 0177 0178 Config::Config() 0179 : QObject(nullptr) 0180 , d(new Private(this)) 0181 { 0182 } 0183 0184 Config::~Config() 0185 { 0186 delete d; 0187 } 0188 0189 ConfigPtr Config::clone() const 0190 { 0191 ConfigPtr newConfig(new Config()); 0192 newConfig->d->screen = d->screen->clone(); 0193 newConfig->setSupportedFeatures(supportedFeatures()); 0194 newConfig->setTabletModeAvailable(tabletModeAvailable()); 0195 newConfig->setTabletModeEngaged(tabletModeEngaged()); 0196 for (const OutputPtr &ourOutput : std::as_const(d->outputs)) { 0197 newConfig->addOutput(ourOutput->clone()); 0198 } 0199 return newConfig; 0200 } 0201 0202 QString Config::connectedOutputsHash() const 0203 { 0204 QStringList hashedOutputs; 0205 0206 const auto outputs = connectedOutputs(); 0207 hashedOutputs.reserve(outputs.count()); 0208 for (const OutputPtr &output : outputs) { 0209 hashedOutputs << output->hash(); 0210 } 0211 std::sort(hashedOutputs.begin(), hashedOutputs.end()); 0212 const auto hash = QCryptographicHash::hash(hashedOutputs.join(QString()).toLatin1(), QCryptographicHash::Md5); 0213 return QString::fromLatin1(hash.toHex()); 0214 } 0215 0216 ScreenPtr Config::screen() const 0217 { 0218 return d->screen; 0219 } 0220 0221 void Config::setScreen(const ScreenPtr &screen) 0222 { 0223 d->screen = screen; 0224 } 0225 0226 OutputPtr Config::output(int outputId) const 0227 { 0228 return d->outputs.value(outputId); 0229 } 0230 0231 Config::Features Config::supportedFeatures() const 0232 { 0233 return d->supportedFeatures; 0234 } 0235 0236 void Config::setSupportedFeatures(const Config::Features &features) 0237 { 0238 d->supportedFeatures = features; 0239 } 0240 0241 bool Config::tabletModeAvailable() const 0242 { 0243 return d->tabletModeAvailable; 0244 } 0245 0246 void Config::setTabletModeAvailable(bool available) 0247 { 0248 d->tabletModeAvailable = available; 0249 } 0250 0251 bool Config::tabletModeEngaged() const 0252 { 0253 return d->tabletModeEngaged; 0254 } 0255 0256 void Config::setTabletModeEngaged(bool engaged) 0257 { 0258 d->tabletModeEngaged = engaged; 0259 } 0260 0261 OutputList Config::outputs() const 0262 { 0263 return d->outputs; 0264 } 0265 0266 OutputList Config::connectedOutputs() const 0267 { 0268 OutputList outputs; 0269 for (const OutputPtr &output : std::as_const(d->outputs)) { 0270 if (!output->isConnected()) { 0271 continue; 0272 } 0273 outputs.insert(output->id(), output); 0274 } 0275 0276 return outputs; 0277 } 0278 0279 OutputPtr Config::primaryOutput() const 0280 { 0281 return d->findPrimaryOutput(); 0282 } 0283 0284 void Config::setPrimaryOutput(const OutputPtr &newPrimary) 0285 { 0286 setOutputPriority(newPrimary, 1); 0287 } 0288 0289 void Config::addOutput(const OutputPtr &output) 0290 { 0291 d->outputs.insert(output->id(), output); 0292 output->setExplicitLogicalSize(logicalSizeForOutput(*output)); 0293 0294 Q_EMIT outputAdded(output); 0295 } 0296 0297 void Config::removeOutput(int outputId) 0298 { 0299 d->removeOutput(d->outputs.find(outputId)); 0300 } 0301 0302 void Config::setOutputs(const OutputList &outputs) 0303 { 0304 for (auto iter = d->outputs.begin(), end = d->outputs.end(); iter != end;) { 0305 iter = d->removeOutput(iter); 0306 end = d->outputs.end(); 0307 } 0308 0309 for (const OutputPtr &output : outputs) { 0310 addOutput(output); 0311 } 0312 0313 adjustPriorities(); 0314 } 0315 0316 void Config::setOutputPriority(const OutputPtr &output, uint32_t priority) 0317 { 0318 if (!d->outputs.contains(output->id()) || d->outputs[output->id()] != output) { 0319 qCDebug(KSCREEN) << "The output" << output << "does not belong to this config"; 0320 return; 0321 } 0322 if (output->priority() == priority) { 0323 return; 0324 } 0325 output->setEnabled(priority != 0); 0326 output->setPriority(priority); 0327 adjustPriorities((priority != 0) ? std::optional(output) : std::nullopt); 0328 } 0329 0330 void Config::setOutputPriorities(QMap<OutputPtr, uint32_t> &priorities) 0331 { 0332 for (auto it = priorities.constBegin(); it != priorities.constEnd(); it++) { 0333 const OutputPtr &output = it.key(); 0334 const uint32_t priority = it.value(); 0335 0336 if (!d->outputs.contains(output->id()) || d->outputs[output->id()] != output) { 0337 qCDebug(KSCREEN) << "The output" << output << "does not belong to this config"; 0338 return; 0339 } 0340 output->setEnabled(priority != 0); 0341 output->setPriority(priority); 0342 } 0343 adjustPriorities(); 0344 } 0345 0346 static std::optional<OutputPtr> removeOptional(QList<OutputPtr> &haystack, std::optional<OutputPtr> &needle) 0347 { 0348 if (!needle.has_value()) { 0349 return std::nullopt; 0350 } 0351 const OutputPtr &value = needle.value(); 0352 const bool removed = haystack.removeOne(value); 0353 return removed ? needle : std::nullopt; 0354 } 0355 0356 void Config::adjustPriorities(std::optional<OutputPtr> keep) 0357 { 0358 // we need specifically tree-based QMap for this 0359 QMap<uint32_t, QList<OutputPtr>> multimap; 0360 uint32_t maxPriority = 0; 0361 bool found = false; 0362 0363 for (const OutputPtr &output : d->outputs) { 0364 maxPriority = std::max(maxPriority, output->priority()); 0365 } 0366 0367 if (keep.has_value() && keep.value()->priority() == 0) { 0368 qCDebug(KSCREEN) << "The output to keep" << keep.value() << "has zero priority. Did you forget to set priority after enabling it?"; 0369 keep.reset(); 0370 } 0371 for (const OutputPtr &output : d->outputs) { 0372 if (keep.has_value() && keep.value() == output) { 0373 found = true; 0374 } 0375 if (!output->isEnabled()) { 0376 output->setPriority(0); 0377 } else { 0378 // XXX: we are currently not enforcing consistency after enabling an output. 0379 if (output->priority() == 0) { 0380 output->setPriority(maxPriority + 1); 0381 } 0382 QList<OutputPtr> &entry = multimap[output->priority()]; 0383 entry.append(output); 0384 } 0385 } 0386 if (keep.has_value() && !found) { 0387 qCDebug(KSCREEN) << "The output to keep" << keep.value() << "is not in the list of outputs" << d->outputs; 0388 keep.reset(); 0389 } 0390 0391 uint32_t nextPriority = 1; 0392 for (QList<OutputPtr> ¤t_list : multimap) { 0393 std::optional<OutputPtr> currentKeep = removeOptional(current_list, keep); 0394 0395 // deterministic sorting of identically-prioritized outputs. 0396 // ordering reversed, so that later we can use pop() operation instead of removing from the beginning. 0397 std::stable_sort(current_list.begin(), current_list.end(), [](const OutputPtr &lhs, const OutputPtr &rhs) -> bool { 0398 return rhs->name() < lhs->name(); 0399 }); 0400 0401 while (currentKeep.has_value() || !current_list.isEmpty()) { 0402 OutputPtr nextOutput; 0403 if (currentKeep.has_value() && (currentKeep.value()->priority() <= nextPriority || current_list.isEmpty())) { 0404 nextOutput = currentKeep.value(); 0405 currentKeep.reset(); 0406 } else { 0407 Q_ASSERT(!current_list.isEmpty()); 0408 nextOutput = current_list.takeLast(); 0409 } 0410 nextOutput->setPriority(nextPriority); 0411 nextPriority += 1; 0412 } 0413 } 0414 0415 Q_EMIT prioritiesChanged(); 0416 } 0417 0418 bool Config::isValid() const 0419 { 0420 return d->valid; 0421 } 0422 0423 void Config::setValid(bool valid) 0424 { 0425 d->valid = valid; 0426 } 0427 0428 void Config::apply(const ConfigPtr &other) 0429 { 0430 d->screen->apply(other->screen()); 0431 0432 setTabletModeAvailable(other->tabletModeAvailable()); 0433 setTabletModeEngaged(other->tabletModeEngaged()); 0434 0435 // Remove removed outputs 0436 for (auto it = d->outputs.begin(); it != d->outputs.end();) { 0437 if (!other->d->outputs.contains((*it)->id())) { 0438 it = d->removeOutput(it); 0439 } else { 0440 ++it; 0441 } 0442 } 0443 0444 for (const OutputPtr &otherOutput : std::as_const(other->d->outputs)) { 0445 // Add new outputs 0446 if (!d->outputs.contains(otherOutput->id())) { 0447 addOutput(otherOutput->clone()); 0448 } else { 0449 // Update existing outputs 0450 d->outputs[otherOutput->id()]->apply(otherOutput); 0451 d->outputs[otherOutput->id()]->setExplicitLogicalSize(logicalSizeForOutput(*d->outputs[otherOutput->id()])); 0452 } 0453 } 0454 0455 // Update validity 0456 setValid(other->isValid()); 0457 0458 Q_EMIT prioritiesChanged(); 0459 } 0460 0461 QRect Config::outputGeometryForOutput(const KScreen::Output &output) const 0462 { 0463 QSize size = logicalSizeForOutput(output).toSize(); 0464 if (!size.isValid()) { 0465 return QRect(); 0466 } 0467 0468 return QRect(output.pos(), size); 0469 } 0470 0471 QSizeF Config::logicalSizeForOutput(const KScreen::Output &output) const 0472 { 0473 QSizeF size = output.enforcedModeSize(); 0474 if (!size.isValid()) { 0475 return QSizeF(); 0476 } 0477 // ignore scale where scaling is not per-output 0478 if (supportedFeatures().testFlag(Feature::PerOutputScaling)) { 0479 size = size / output.scale(); 0480 } 0481 0482 // We can't use output.size(), because it does not reflect the actual rotation() set by caller. 0483 // It is only updated when we get update from KScreen, but not when user changes mode or 0484 // rotation manually. 0485 0486 if (!output.isHorizontal()) { 0487 size = size.transposed(); 0488 } 0489 return size; 0490 } 0491 0492 QDebug operator<<(QDebug dbg, const KScreen::ConfigPtr &config) 0493 { 0494 if (config) { 0495 dbg << "KScreen::Config("; 0496 const auto outputs = config->outputs(); 0497 for (const auto &output : outputs) { 0498 if (output->isConnected()) { 0499 dbg << Qt::endl << output; 0500 } 0501 } 0502 dbg << ")"; 0503 } else { 0504 dbg << "KScreen::Config(NULL)"; 0505 } 0506 return dbg; 0507 } 0508 0509 #include "config.moc"