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

0001 /*
0002     SPDX-FileCopyrightText: 2012 Alejandro Fiestas Olivares <afiestas@kde.org>
0003     SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 #include "config.h"
0008 #include "../common/control.h"
0009 #include "device.h"
0010 #include "kscreen_daemon_debug.h"
0011 #include "output.h"
0012 
0013 #include <QDir>
0014 #include <QFile>
0015 #include <QJsonDocument>
0016 #include <QRect>
0017 #include <QStandardPaths>
0018 #include <QStringBuilder>
0019 
0020 #include <cstdint>
0021 
0022 #include <kscreen/output.h>
0023 #include <kscreen/screen.h>
0024 
0025 QString Config::s_fixedConfigFileName = QStringLiteral("fixed-config");
0026 QString Config::s_configsDirName = QString();
0027 /*QStringLiteral("configs");*/ // TODO: KDE6 - Replace QString w/ QStringLiteral move these files into the subfolder
0028 
0029 QString Config::configsDirPath()
0030 {
0031     return Globals::dirPath() % s_configsDirName;
0032 }
0033 
0034 Config::Config(KScreen::ConfigPtr config, QObject *parent)
0035     : QObject(parent)
0036     , m_data(config)
0037     , m_control(new ControlConfig(config, this))
0038 {
0039 }
0040 
0041 QString Config::filePath() const
0042 {
0043     if (!QDir().mkpath(configsDirPath())) {
0044         return QString();
0045     }
0046     return configsDirPath() % id();
0047 }
0048 
0049 QString Config::id() const
0050 {
0051     if (!m_data) {
0052         return QString();
0053     }
0054     return m_data->connectedOutputsHash();
0055 }
0056 
0057 void Config::activateControlWatching()
0058 {
0059     connect(m_control, &ControlConfig::changed, this, &Config::controlChanged);
0060     m_control->activateWatcher();
0061 }
0062 
0063 bool Config::autoRotationRequested() const
0064 {
0065     for (KScreen::OutputPtr &output : m_data->outputs()) {
0066         if (m_control->getAutoRotate(output)) {
0067             return true;
0068         }
0069     }
0070     return false;
0071 }
0072 
0073 void Config::setDeviceOrientation(QOrientationReading::Orientation orientation)
0074 {
0075     for (KScreen::OutputPtr &output : m_data->outputs()) {
0076         if (!m_control->getAutoRotate(output)) {
0077             continue;
0078         }
0079         auto finalOrientation = orientation;
0080         if (m_control->getAutoRotateOnlyInTabletMode(output) && !m_data->tabletModeEngaged()) {
0081             finalOrientation = QOrientationReading::Orientation::TopUp;
0082         }
0083         if (Output::updateOrientation(output, finalOrientation)) {
0084             // TODO: call Layouter to find fitting positions for other outputs again
0085             return;
0086         }
0087     }
0088 }
0089 
0090 bool Config::getAutoRotate() const
0091 {
0092     const auto outputs = m_data->outputs();
0093     return std::all_of(outputs.cbegin(), outputs.cend(), [this](KScreen::OutputPtr output) {
0094         if (output->type() != KScreen::Output::Type::Panel) {
0095             return true;
0096         }
0097         return m_control->getAutoRotate(output);
0098     });
0099 }
0100 
0101 void Config::setAutoRotate(bool value)
0102 {
0103     for (KScreen::OutputPtr &output : m_data->outputs()) {
0104         if (output->type() != KScreen::Output::Type::Panel) {
0105             continue;
0106         }
0107         if (m_control->getAutoRotate(output) != value) {
0108             m_control->setAutoRotate(output, value);
0109         }
0110     }
0111     m_control->writeFile();
0112 }
0113 
0114 bool Config::fileExists() const
0115 {
0116     return (QFile::exists(configsDirPath() % id()) || QFile::exists(configsDirPath() % s_fixedConfigFileName));
0117 }
0118 
0119 std::unique_ptr<Config> Config::readFile()
0120 {
0121     if (Device::self()->isLaptop() && !Device::self()->isLidClosed()) {
0122         // We may look for a config that has been set when the lid was closed, Bug: 353029
0123         const QString lidOpenedFilePath(filePath() % QStringLiteral("_lidOpened"));
0124         const QFile srcFile(lidOpenedFilePath);
0125 
0126         if (srcFile.exists()) {
0127             QFile::remove(filePath());
0128             if (QFile::copy(lidOpenedFilePath, filePath())) {
0129                 QFile::remove(lidOpenedFilePath);
0130                 qCDebug(KSCREEN_KDED) << "Restored lid opened config to" << id();
0131             }
0132         }
0133     }
0134     return readFile(id());
0135 }
0136 
0137 std::unique_ptr<Config> Config::readOpenLidFile()
0138 {
0139     const QString openLidFile = id() % QStringLiteral("_lidOpened");
0140     auto config = readFile(openLidFile);
0141     QFile::remove(configsDirPath() % openLidFile);
0142     return config;
0143 }
0144 
0145 std::unique_ptr<Config> Config::readFile(const QString &fileName)
0146 {
0147     if (!m_data) {
0148         return nullptr;
0149     }
0150     auto config = std::unique_ptr<Config>(new Config(m_data->clone()));
0151     config->setValidityFlags(m_validityFlags);
0152 
0153     QFile file;
0154     if (QFile::exists(configsDirPath() % s_fixedConfigFileName)) {
0155         file.setFileName(configsDirPath() % s_fixedConfigFileName);
0156         qCDebug(KSCREEN_KDED) << "found a fixed config, will use " << file.fileName();
0157     } else {
0158         file.setFileName(configsDirPath() % fileName);
0159     }
0160     if (!file.open(QIODevice::ReadOnly)) {
0161         qCDebug(KSCREEN_KDED) << "failed to open file" << file.fileName();
0162         return nullptr;
0163     }
0164 
0165     QJsonDocument parser;
0166     QVariantList outputs = parser.fromJson(file.readAll()).toVariant().toList();
0167     Output::readInOutputs(config->data(), outputs);
0168 
0169     QSize screenSize;
0170     const auto configOutputs = config->data()->outputs();
0171     for (const auto &output : configOutputs) {
0172         if (!output->isPositionable()) {
0173             continue;
0174         }
0175 
0176         output->setExplicitLogicalSize(config->data()->logicalSizeForOutput(*output));
0177 
0178         const QRect geom = output->geometry();
0179         if (geom.x() + geom.width() > screenSize.width()) {
0180             screenSize.setWidth(geom.x() + geom.width());
0181         }
0182         if (geom.y() + geom.height() > screenSize.height()) {
0183             screenSize.setHeight(geom.y() + geom.height());
0184         }
0185     }
0186     config->data()->screen()->setCurrentSize(screenSize);
0187 
0188     if (!canBeApplied(config->data())) {
0189         return nullptr;
0190     }
0191     return config;
0192 }
0193 
0194 bool Config::canBeApplied() const
0195 {
0196     return canBeApplied(m_data);
0197 }
0198 
0199 bool Config::canBeApplied(KScreen::ConfigPtr config) const
0200 {
0201 #ifdef KDED_UNIT_TEST
0202     Q_UNUSED(config);
0203     return true;
0204 #else
0205     return KScreen::Config::canBeApplied(config, m_validityFlags);
0206 #endif
0207 }
0208 
0209 bool Config::writeFile()
0210 {
0211     return writeFile(filePath());
0212 }
0213 
0214 bool Config::writeOpenLidFile()
0215 {
0216     return writeFile(filePath() % QStringLiteral("_lidOpened"));
0217 }
0218 
0219 bool Config::writeFile(const QString &filePath)
0220 {
0221     if (id().isEmpty()) {
0222         return false;
0223     }
0224     const KScreen::OutputList outputs = m_data->outputs();
0225 
0226     const auto oldConfig = readFile();
0227     KScreen::OutputList oldOutputs;
0228     if (oldConfig) {
0229         oldOutputs = oldConfig->data()->outputs();
0230     }
0231 
0232     const auto hasDuplicate = [&outputs](const auto output) {
0233         return std::any_of(outputs.begin(), outputs.end(), [output](const auto &o) {
0234             return o != output && o->hashMd5() == output->hashMd5();
0235         });
0236     };
0237 
0238     QVariantList outputList;
0239     for (const KScreen::OutputPtr &output : outputs) {
0240         QVariantMap info;
0241 
0242         const auto oldOutputIt = std::find_if(oldOutputs.constBegin(), oldOutputs.constEnd(), [output](const KScreen::OutputPtr &out) {
0243             return out->hashMd5() == output->hashMd5();
0244         });
0245         const KScreen::OutputPtr oldOutput = oldOutputIt != oldOutputs.constEnd() ? *oldOutputIt : nullptr;
0246 
0247         if (!output->isConnected()) {
0248             continue;
0249         }
0250 
0251         Output::writeGlobalPart(output, info, oldOutput);
0252         info[QStringLiteral("priority")] = output->priority();
0253         info[QStringLiteral("enabled")] = output->isEnabled();
0254 
0255         auto setOutputConfigInfo = [&info](const KScreen::OutputPtr &out) {
0256             if (!out) {
0257                 return;
0258             }
0259 
0260             QVariantMap pos;
0261             pos[QStringLiteral("x")] = out->pos().x();
0262             pos[QStringLiteral("y")] = out->pos().y();
0263             info[QStringLiteral("pos")] = pos;
0264         };
0265         setOutputConfigInfo(output->isEnabled() ? output : oldOutput);
0266 
0267         if (output->isEnabled() && m_control->getOutputRetention(output->hash(), output->name()) != Control::OutputRetention::Individual) {
0268             // try to update global output data
0269             Output::writeGlobal(output, hasDuplicate(output));
0270         }
0271 
0272         outputList.append(info);
0273     }
0274 
0275     QFile file(filePath);
0276     if (!file.open(QIODevice::WriteOnly)) {
0277         qCWarning(KSCREEN_KDED) << "Failed to open config file for writing! " << file.errorString();
0278         return false;
0279     }
0280     file.write(QJsonDocument::fromVariant(outputList).toJson());
0281     qCDebug(KSCREEN_KDED) << "Config saved on: " << file.fileName();
0282 
0283     return true;
0284 }
0285 
0286 void Config::log()
0287 {
0288     if (!m_data) {
0289         return;
0290     }
0291     const auto outputs = m_data->outputs();
0292     for (const auto &o : outputs) {
0293         if (o->isConnected()) {
0294             qCDebug(KSCREEN_KDED) << o;
0295         }
0296     }
0297 }