File indexing completed on 2024-04-28 16:45:06

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     // TODO: put this into m_initialControl
0049     m_initialRetention = getRetention();
0050     Q_EMIT retentionChanged();
0051 
0052     connect(m_outputModel, &OutputModel::dataChanged, this, [this](const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles) {
0053         Q_UNUSED(bottomRight)
0054         // Do not run checks during interactive reaarange
0055         if (!m_outputModel->isMoving()) {
0056             checkNeedsSave();
0057         }
0058         Q_EMIT changed();
0059     });
0060     connect(m_config.data(), &KScreen::Config::outputAdded, this, [this]() {
0061         Q_EMIT outputConnect(true);
0062     });
0063     connect(m_config.data(), &KScreen::Config::outputRemoved, this, [this]() {
0064         Q_EMIT outputConnect(false);
0065     });
0066     connect(m_config.data(), &KScreen::Config::prioritiesChanged, this, &ConfigHandler::outputPrioritiesChanged);
0067 
0068     Q_EMIT outputModelChanged();
0069 }
0070 
0071 void ConfigHandler::initOutput(const KScreen::OutputPtr &output)
0072 {
0073     output->setExplicitLogicalSize(config()->logicalSizeForOutput(*output));
0074 
0075     if (output->isConnected()) {
0076         m_outputModel->add(output);
0077     }
0078     connect(output.data(), &KScreen::Output::isConnectedChanged, this, [this, output]() {
0079         Q_EMIT outputConnect(output->isConnected());
0080     });
0081 }
0082 
0083 void ConfigHandler::updateInitialData()
0084 {
0085     m_previousConfig = m_initialConfig->clone();
0086     m_initialRetention = getRetention();
0087     connect(new GetConfigOperation(), &GetConfigOperation::finished, this, [this](ConfigOperation *op) {
0088         if (op->hasError()) {
0089             return;
0090         }
0091         m_initialConfig = qobject_cast<GetConfigOperation *>(op)->config();
0092         m_initialControl.reset(new ControlConfig(m_initialConfig));
0093         checkNeedsSave();
0094     });
0095 }
0096 
0097 bool ConfigHandler::shouldTestNewSettings()
0098 {
0099     return checkSaveandTestCommon(false);
0100 }
0101 
0102 void ConfigHandler::checkNeedsSave()
0103 {
0104     if (checkPrioritiesNeedSave()) {
0105         Q_EMIT needsSaveChecked(true);
0106         return;
0107     }
0108     if (m_initialRetention != getRetention()) {
0109         Q_EMIT needsSaveChecked(true);
0110         return;
0111     }
0112     Q_EMIT needsSaveChecked(checkSaveandTestCommon(true));
0113 }
0114 
0115 bool ConfigHandler::checkPrioritiesNeedSave()
0116 {
0117     if (!(m_config->supportedFeatures() & KScreen::Config::Feature::PrimaryDisplay)) {
0118         return false;
0119     }
0120     // first item of pair is initial config, second is current
0121     QMap<QString, std::pair<std::optional<uint32_t>, std::optional<uint32_t>>> map;
0122 
0123     // exploiting the fact that operator[] on a map is what's called get_or_insert_default in other languages
0124     const auto &initialList = m_initialConfig->outputs();
0125     for (const OutputPtr &output : initialList) {
0126         map[output->hashMd5()].first = std::optional(output->priority());
0127     }
0128     const auto &currentList = m_config->outputs();
0129     for (const OutputPtr &output : currentList) {
0130         map[output->hashMd5()].second = std::optional(output->priority());
0131     }
0132     // so if we end up with items that are not both initialized to the same priority
0133     for (const auto &[left, right] : std::as_const(map)) {
0134         if (!(left.has_value() && right.has_value() && left.value() == right.value())) {
0135             // then configs must be different after all
0136             return true;
0137         }
0138     }
0139     return false;
0140 }
0141 
0142 bool ConfigHandler::checkSaveandTestCommon(bool isSaveCheck)
0143 {
0144     const auto outputs = m_config->connectedOutputs();
0145     for (const auto &output : outputs) {
0146         const QString hash = output->hashMd5();
0147         const auto configs = m_initialConfig->outputs();
0148         for (const auto &config : configs) {
0149             if (hash != config->hashMd5()) {
0150                 continue;
0151             }
0152 
0153             if (output->isEnabled() != config->isEnabled()) {
0154                 return true;
0155             }
0156 
0157             // clang-format off
0158             if (output->isEnabled()) {
0159                 bool scaleChanged = false;
0160                 if (isSaveCheck || m_config->supportedFeatures() & KScreen::Config::Feature::PerOutputScaling) {
0161                      scaleChanged = output->scale() != config->scale();
0162                 }
0163                 if ( output->currentModeId() != config->currentModeId()
0164                     || output->pos() != config->pos()
0165                     || scaleChanged
0166                     || output->rotation() != config->rotation()
0167                     || output->replicationSource() != config->replicationSource()
0168                     || autoRotate(output) != m_initialControl->getAutoRotate(output)
0169                     || autoRotateOnlyInTabletMode(output) != m_initialControl->getAutoRotateOnlyInTabletMode(output)
0170                     || output->overscan() != config->overscan()
0171                     || output->vrrPolicy() != config->vrrPolicy()
0172                     || output->rgbRange() != config->rgbRange()) {
0173                         return true;
0174                     }
0175             }
0176             // clang-format on
0177         }
0178     }
0179     return false;
0180 }
0181 
0182 QSize ConfigHandler::screenSize() const
0183 {
0184     int width = 0, height = 0;
0185     QSize size;
0186 
0187     const auto connectedOutputs = m_config->connectedOutputs();
0188     for (const auto &output : connectedOutputs) {
0189         if (!output->isPositionable()) {
0190             continue;
0191         }
0192         const int outputRight = output->geometry().right();
0193         const int outputBottom = output->geometry().bottom();
0194 
0195         if (outputRight > width) {
0196             width = outputRight;
0197         }
0198         if (outputBottom > height) {
0199             height = outputBottom;
0200         }
0201     }
0202     if (width > 0 && height > 0) {
0203         size = QSize(width, height);
0204     } else {
0205         size = QSize();
0206     }
0207     return size;
0208 }
0209 
0210 QSize ConfigHandler::normalizeScreen()
0211 {
0212     if (!m_config) {
0213         return QSize();
0214     }
0215 
0216     m_outputModel->normalizePositions();
0217     const auto currentScreenSize = screenSize();
0218     m_lastNormalizedScreenSize = currentScreenSize;
0219 
0220     Q_EMIT screenNormalizationUpdate(true);
0221     return currentScreenSize;
0222 }
0223 
0224 void ConfigHandler::checkScreenNormalization()
0225 {
0226     const bool normalized = !m_config || (m_lastNormalizedScreenSize == screenSize() && m_outputModel->positionsNormalized());
0227 
0228     Q_EMIT screenNormalizationUpdate(normalized);
0229 }
0230 
0231 void ConfigHandler::outputPrioritiesChanged()
0232 {
0233     checkNeedsSave();
0234     Q_EMIT changed();
0235 }
0236 
0237 Control::OutputRetention ConfigHandler::getRetention() const
0238 {
0239     using Retention = Control::OutputRetention;
0240 
0241     auto ret = Retention::Undefined;
0242     if (!m_control) {
0243         return ret;
0244     }
0245     const auto outputs = m_config->connectedOutputs();
0246     if (outputs.isEmpty()) {
0247         return ret;
0248     }
0249     ret = m_control->getOutputRetention(outputs.first());
0250 
0251     for (const auto &output : outputs) {
0252         const auto outputRet = m_control->getOutputRetention(output);
0253         if (ret != outputRet) {
0254             // Control file with different retention values per output.
0255             return Retention::Undefined;
0256         }
0257     }
0258 
0259     if (ret == Retention::Undefined) {
0260         // If all outputs have undefined retention,
0261         // this should be displayed as global retention.
0262         return Retention::Global;
0263     }
0264     return ret;
0265 }
0266 
0267 int ConfigHandler::retention() const
0268 {
0269     return static_cast<int>(getRetention());
0270 }
0271 
0272 void ConfigHandler::setRetention(int retention)
0273 {
0274     using Retention = Control::OutputRetention;
0275 
0276     if (!m_control) {
0277         return;
0278     }
0279     if (retention != static_cast<int>(Retention::Global) && retention != static_cast<int>(Retention::Individual)) {
0280         // We only allow setting to global or individual retention.
0281         return;
0282     }
0283     if (retention == ConfigHandler::retention()) {
0284         return;
0285     }
0286     auto ret = static_cast<Retention>(retention);
0287     const auto connectedOutputs = m_config->connectedOutputs();
0288     for (const auto &output : connectedOutputs) {
0289         m_control->setOutputRetention(output, ret);
0290     }
0291     checkNeedsSave();
0292     Q_EMIT retentionChanged();
0293     Q_EMIT changed();
0294 }
0295 
0296 KScreen::OutputPtr ConfigHandler::replicationSource(const KScreen::OutputPtr &output) const
0297 {
0298     return m_control->getReplicationSource(output);
0299 }
0300 
0301 void ConfigHandler::setReplicationSource(KScreen::OutputPtr &output, const KScreen::OutputPtr &source)
0302 {
0303     m_control->setReplicationSource(output, source);
0304 }
0305 
0306 bool ConfigHandler::autoRotate(const KScreen::OutputPtr &output) const
0307 {
0308     return m_control->getAutoRotate(output);
0309 }
0310 
0311 void ConfigHandler::setAutoRotate(KScreen::OutputPtr &output, bool autoRotate)
0312 {
0313     m_control->setAutoRotate(output, autoRotate);
0314 }
0315 
0316 bool ConfigHandler::autoRotateOnlyInTabletMode(const KScreen::OutputPtr &output) const
0317 {
0318     return m_control->getAutoRotateOnlyInTabletMode(output);
0319 }
0320 
0321 void ConfigHandler::setAutoRotateOnlyInTabletMode(KScreen::OutputPtr &output, bool value)
0322 {
0323     m_control->setAutoRotateOnlyInTabletMode(output, value);
0324 }
0325 
0326 uint32_t ConfigHandler::overscan(const KScreen::OutputPtr &output) const
0327 {
0328     return m_control->getOverscan(output);
0329 }
0330 
0331 void ConfigHandler::setOverscan(const KScreen::OutputPtr &output, uint32_t value)
0332 {
0333     m_control->setOverscan(output, value);
0334 }
0335 
0336 KScreen::Output::VrrPolicy ConfigHandler::vrrPolicy(const KScreen::OutputPtr &output) const
0337 {
0338     return m_control->getVrrPolicy(output);
0339 }
0340 
0341 void ConfigHandler::setVrrPolicy(const KScreen::OutputPtr &output, KScreen::Output::VrrPolicy value)
0342 {
0343     m_control->setVrrPolicy(output, value);
0344 }
0345 
0346 KScreen::Output::RgbRange ConfigHandler::rgbRange(const KScreen::OutputPtr &output) const
0347 {
0348     return m_control->getRgbRange(output);
0349 }
0350 
0351 void ConfigHandler::setRgbRange(const KScreen::OutputPtr &output, KScreen::Output::RgbRange value)
0352 {
0353     m_control->setRgbRange(output, value);
0354 }
0355 
0356 void ConfigHandler::writeControl()
0357 {
0358     if (!m_control) {
0359         return;
0360     }
0361     m_control->writeFile();
0362 }