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> &current_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"