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

0001 /*
0002     SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 #include "config_handler.h"
0007 
0008 #include "kcm_screen_debug.h"
0009 #include "output_model.h"
0010 
0011 #include <algorithm>
0012 #include <cstdint>
0013 #include <utility>
0014 
0015 #include <kscreen/configmonitor.h>
0016 #include <kscreen/getconfigoperation.h>
0017 #include <kscreen/output.h>
0018 
0019 #include <QGuiApplication>
0020 #include <QRect>
0021 
0022 using namespace KScreen;
0023 
0024 ConfigHandler::ConfigHandler(QObject *parent)
0025     : QObject(parent)
0026 {
0027 }
0028 
0029 void ConfigHandler::setConfig(KScreen::ConfigPtr config)
0030 {
0031     m_config = config;
0032     m_initialConfig = m_config->clone();
0033     m_initialControl.reset(new ControlConfig(m_initialConfig));
0034 
0035     KScreen::ConfigMonitor::instance()->addConfig(m_config);
0036     m_control.reset(new ControlConfig(config));
0037 
0038     m_outputModel = new OutputModel(this);
0039     connect(m_outputModel, &OutputModel::positionChanged, this, &ConfigHandler::checkScreenNormalization);
0040     connect(m_outputModel, &OutputModel::sizeChanged, this, &ConfigHandler::checkScreenNormalization);
0041 
0042     const auto outputs = config->outputs();
0043     for (const KScreen::OutputPtr &output : outputs) {
0044         initOutput(output);
0045     }
0046     m_lastNormalizedScreenSize = screenSize();
0047 
0048     connect(m_outputModel, &OutputModel::dataChanged, this, [this](const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList<int> &roles) {
0049         Q_UNUSED(bottomRight)
0050         // Do not run checks during interactive reaarange
0051         if (!m_outputModel->isMoving()) {
0052             checkNeedsSave();
0053         }
0054         Q_EMIT changed();
0055     });
0056     connect(m_config.data(), &KScreen::Config::outputAdded, this, [this]() {
0057         Q_EMIT outputConnect(true);
0058     });
0059     connect(m_config.data(), &KScreen::Config::outputRemoved, this, [this]() {
0060         Q_EMIT outputConnect(false);
0061     });
0062     connect(m_config.data(), &KScreen::Config::prioritiesChanged, this, &ConfigHandler::outputPrioritiesChanged);
0063 
0064     Q_EMIT outputModelChanged();
0065 }
0066 
0067 void ConfigHandler::initOutput(const KScreen::OutputPtr &output)
0068 {
0069     output->setExplicitLogicalSize(config()->logicalSizeForOutput(*output));
0070 
0071     if (output->isConnected()) {
0072         m_outputModel->add(output);
0073     }
0074     connect(output.data(), &KScreen::Output::isConnectedChanged, this, [this, output]() {
0075         Q_EMIT outputConnect(output->isConnected());
0076     });
0077 }
0078 
0079 void ConfigHandler::updateInitialData()
0080 {
0081     m_previousConfig = m_initialConfig->clone();
0082     connect(new GetConfigOperation(), &GetConfigOperation::finished, this, [this](ConfigOperation *op) {
0083         if (op->hasError()) {
0084             return;
0085         }
0086         m_initialConfig = qobject_cast<GetConfigOperation *>(op)->config();
0087         m_initialControl.reset(new ControlConfig(m_initialConfig));
0088         checkNeedsSave();
0089     });
0090 }
0091 
0092 bool ConfigHandler::shouldTestNewSettings()
0093 {
0094     return checkSaveandTestCommon(false);
0095 }
0096 
0097 void ConfigHandler::checkNeedsSave()
0098 {
0099     if (checkPrioritiesNeedSave()) {
0100         Q_EMIT needsSaveChecked(true);
0101         return;
0102     }
0103     Q_EMIT needsSaveChecked(checkSaveandTestCommon(true));
0104 }
0105 
0106 bool ConfigHandler::checkPrioritiesNeedSave()
0107 {
0108     if (!(m_config->supportedFeatures() & KScreen::Config::Feature::PrimaryDisplay)) {
0109         return false;
0110     }
0111     // first item of pair is initial config, second is current
0112     QMap<QString, std::pair<std::optional<uint32_t>, std::optional<uint32_t>>> map;
0113 
0114     // exploiting the fact that operator[] on a map is what's called get_or_insert_default in other languages
0115     const auto &initialList = m_initialConfig->outputs();
0116     for (const OutputPtr &output : initialList) {
0117         map[output->hashMd5()].first = std::optional(output->priority());
0118     }
0119     const auto &currentList = m_config->outputs();
0120     for (const OutputPtr &output : currentList) {
0121         map[output->hashMd5()].second = std::optional(output->priority());
0122     }
0123     // so if we end up with items that are not both initialized to the same priority
0124     for (const auto &[left, right] : std::as_const(map)) {
0125         if (!(left.has_value() && right.has_value() && left.value() == right.value())) {
0126             // then configs must be different after all
0127             return true;
0128         }
0129     }
0130     return false;
0131 }
0132 
0133 bool ConfigHandler::checkSaveandTestCommon(bool isSaveCheck)
0134 {
0135     const auto outputs = m_config->connectedOutputs();
0136     for (const auto &output : outputs) {
0137         const QString hash = output->hashMd5();
0138         const auto configs = m_initialConfig->outputs();
0139         for (const auto &config : configs) {
0140             if (hash != config->hashMd5()) {
0141                 continue;
0142             }
0143 
0144             if (output->isEnabled() != config->isEnabled()) {
0145                 return true;
0146             }
0147 
0148             // clang-format off
0149             if (output->isEnabled()) {
0150                 bool scaleChanged = false;
0151                 if (isSaveCheck || m_config->supportedFeatures() & KScreen::Config::Feature::PerOutputScaling) {
0152                      scaleChanged = output->scale() != config->scale();
0153                 }
0154                 if ( output->currentModeId() != config->currentModeId()
0155                     || output->pos() != config->pos()
0156                     || scaleChanged
0157                     || output->rotation() != config->rotation()
0158                     || output->replicationSource() != config->replicationSource()
0159                     || output->overscan() != config->overscan()
0160                     || output->vrrPolicy() != config->vrrPolicy()
0161                     || output->rgbRange() != config->rgbRange()
0162                     || output->autoRotatePolicy() != config->autoRotatePolicy()
0163                     || output->iccProfilePath() != config->iccProfilePath()
0164                     || output->isHdrEnabled() != config->isHdrEnabled()
0165                     || output->isWcgEnabled() != config->isWcgEnabled()
0166                     || output->sdrBrightness() != config->sdrBrightness()
0167                     || output->sdrGamutWideness() != config->sdrGamutWideness()) {
0168                         return true;
0169                     }
0170             }
0171             // clang-format on
0172         }
0173     }
0174     return false;
0175 }
0176 
0177 QSize ConfigHandler::screenSize() const
0178 {
0179     int width = 0, height = 0;
0180     QSize size;
0181 
0182     const auto connectedOutputs = m_config->connectedOutputs();
0183     for (const auto &output : connectedOutputs) {
0184         if (!output->isPositionable()) {
0185             continue;
0186         }
0187         const int outputRight = output->geometry().right();
0188         const int outputBottom = output->geometry().bottom();
0189 
0190         if (outputRight > width) {
0191             width = outputRight;
0192         }
0193         if (outputBottom > height) {
0194             height = outputBottom;
0195         }
0196     }
0197     if (width > 0 && height > 0) {
0198         size = QSize(width, height);
0199     } else {
0200         size = QSize();
0201     }
0202     return size;
0203 }
0204 
0205 QSize ConfigHandler::normalizeScreen()
0206 {
0207     if (!m_config) {
0208         return QSize();
0209     }
0210 
0211     m_outputModel->normalizePositions();
0212     const auto currentScreenSize = screenSize();
0213     m_lastNormalizedScreenSize = currentScreenSize;
0214 
0215     Q_EMIT screenNormalizationUpdate(true);
0216     return currentScreenSize;
0217 }
0218 
0219 void ConfigHandler::checkScreenNormalization()
0220 {
0221     const bool normalized = !m_config || (m_lastNormalizedScreenSize == screenSize() && m_outputModel->positionsNormalized());
0222 
0223     Q_EMIT screenNormalizationUpdate(normalized);
0224 }
0225 
0226 void ConfigHandler::outputPrioritiesChanged()
0227 {
0228     checkNeedsSave();
0229     Q_EMIT changed();
0230 }
0231 
0232 KScreen::OutputPtr ConfigHandler::replicationSource(const KScreen::OutputPtr &output) const
0233 {
0234     return m_control->getReplicationSource(output);
0235 }
0236 
0237 void ConfigHandler::setReplicationSource(KScreen::OutputPtr &output, const KScreen::OutputPtr &source)
0238 {
0239     m_control->setReplicationSource(output, source);
0240 }
0241 
0242 uint32_t ConfigHandler::overscan(const KScreen::OutputPtr &output) const
0243 {
0244     return m_control->getOverscan(output);
0245 }
0246 
0247 void ConfigHandler::setOverscan(const KScreen::OutputPtr &output, uint32_t value)
0248 {
0249     m_control->setOverscan(output, value);
0250 }
0251 
0252 KScreen::Output::VrrPolicy ConfigHandler::vrrPolicy(const KScreen::OutputPtr &output) const
0253 {
0254     return m_control->getVrrPolicy(output);
0255 }
0256 
0257 void ConfigHandler::setVrrPolicy(const KScreen::OutputPtr &output, KScreen::Output::VrrPolicy value)
0258 {
0259     m_control->setVrrPolicy(output, value);
0260 }
0261 
0262 KScreen::Output::RgbRange ConfigHandler::rgbRange(const KScreen::OutputPtr &output) const
0263 {
0264     return m_control->getRgbRange(output);
0265 }
0266 
0267 void ConfigHandler::setRgbRange(const KScreen::OutputPtr &output, KScreen::Output::RgbRange value)
0268 {
0269     m_control->setRgbRange(output, value);
0270 }
0271 
0272 void ConfigHandler::writeControl()
0273 {
0274     if (!m_control) {
0275         return;
0276     }
0277     m_control->writeFile();
0278 }
0279 
0280 #include "moc_config_handler.cpp"