diff --git a/common/control.cpp b/common/control.cpp index 258d4f0..7e6e767 100644 --- a/common/control.cpp +++ b/common/control.cpp @@ -1,233 +1,240 @@ /******************************************************************** Copyright 2019 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "control.h" #include "globals.h" #include #include #include #include #include QString Control::s_dirName = QStringLiteral("control/"); +Control::Control(QObject *parent) + : QObject(parent) +{ +} + QString Control::dirPath() const { return Globals::dirPath() % s_dirName; } QString Control::filePathFromHash(const QString &hash) const { return dirPath() % hash; } Control::OutputRetention Control::convertVariantToOutputRetention(QVariant variant) { if (variant.canConvert()) { const auto retention = variant.toInt(); if (retention == (int)OutputRetention::Global) { return OutputRetention::Global; } if (retention == (int)OutputRetention::Individual) { return OutputRetention::Individual; } } return OutputRetention::Undefined; } -ControlConfig::ControlConfig(KScreen::ConfigPtr config) - : m_config(config) +ControlConfig::ControlConfig(KScreen::ConfigPtr config, QObject *parent) + : Control(parent) + , m_config(config) { // qDebug() << "Looking for control file:" << config->connectedOutputsHash(); QFile file(filePathFromHash(config->connectedOutputsHash())); if (file.open(QIODevice::ReadOnly)) { // This might not be reached, bus this is ok. The control file will // eventually be created on first write later on. QJsonDocument parser; m_info = parser.fromJson(file.readAll()).toVariant().toMap(); } // TODO: use a file watcher in case of changes to the control file while // object exists? // As global outputs are indexed by a hash of their edid, which is not unique, // to be able to tell apart multiple identical outputs, these need special treatment { QStringList allIds; const auto outputs = config->outputs(); allIds.reserve(outputs.count()); for (const KScreen::OutputPtr &output : outputs) { const auto outputId = output->hashMd5(); if (allIds.contains(outputId) && !m_duplicateOutputIds.contains(outputId)) { m_duplicateOutputIds << outputId; } allIds << outputId; } } // TODO: this is same in Output::readInOutputs of the daemon. Combine? // TODO: connect to outputs added/removed signals and reevaluate duplicate ids // in case of such a change while object exists? } QString ControlConfig::dirPath() const { return Control::dirPath() % QStringLiteral("configs/"); } QString ControlConfig::filePath() const { if (!m_config) { return QString(); } return filePathFromHash(m_config->connectedOutputsHash()); } bool ControlConfig::infoIsOutput(const QVariantMap &info, const QString &outputId, const QString &outputName) const { const QString outputIdInfo = info[QStringLiteral("id")].toString(); if (outputIdInfo.isEmpty()) { return false; } if (outputId != outputIdInfo) { return false; } if (!outputName.isEmpty() && m_duplicateOutputIds.contains(outputId)) { // We may have identical outputs connected, these will have the same id in the config // in order to find the right one, also check the output's name (usually the connector) const auto metadata = info[QStringLiteral("metadata")].toMap(); const auto outputNameInfo = metadata[QStringLiteral("name")].toString(); if (outputName != outputNameInfo) { // was a duplicate id, but info not for this output return false; } } return true; } Control::OutputRetention ControlConfig::getOutputRetention(const KScreen::OutputPtr &output) const { return getOutputRetention(output->hashMd5(), output->name()); } Control::OutputRetention ControlConfig::getOutputRetention(const QString &outputId, const QString &outputName) const { const QVariantList outputsInfo = getOutputs(); for (const auto variantInfo : outputsInfo) { const QVariantMap info = variantInfo.toMap(); if (!infoIsOutput(info, outputId, outputName)) { continue; } return convertVariantToOutputRetention(info[QStringLiteral("retention")]); } // info for output not found return OutputRetention::Undefined; } static QVariantMap metadata(const QString &outputName) { QVariantMap metadata; metadata[QStringLiteral("name")] = outputName; return metadata; } void ControlConfig::setOutputRetention(const KScreen::OutputPtr &output, OutputRetention value) { setOutputRetention(output->hashMd5(), output->name(), value); } void ControlConfig::setOutputRetention(const QString &outputId, const QString &outputName, OutputRetention value) { QList::iterator it; QVariantList outputsInfo = getOutputs(); for (it = outputsInfo.begin(); it != outputsInfo.end(); ++it) { QVariantMap outputInfo = (*it).toMap(); if (!infoIsOutput(outputInfo, outputId, outputName)) { continue; } outputInfo[QStringLiteral("retention")] = (int)value; *it = outputInfo; setOutputs(outputsInfo); return; } // no entry yet, create one QVariantMap outputInfo; outputInfo[QStringLiteral("id")] = outputId; outputInfo[QStringLiteral("metadata")] = metadata(outputName); outputInfo[QStringLiteral("retention")] = (int)value; outputsInfo << outputInfo; setOutputs(outputsInfo); } bool ControlConfig::writeFile() { const QString path = filePath(); if (m_info.isEmpty()) { // Nothing to write. Default control. Remove file if it exists. QFile::remove(path); return true; } if (!QDir().mkpath(dirPath())) { // TODO: error message return false; } // write updated data to file QFile file(path); if (!file.open(QIODevice::WriteOnly)) { // TODO: logging category? // qCWarning(KSCREEN_COMMON) << "Failed to open config control file for writing! " << file.errorString(); return false; } file.write(QJsonDocument::fromVariant(m_info).toJson()); // qCDebug(KSCREEN_COMMON) << "Config control saved on: " << file.fileName(); return true; } QVariantList ControlConfig::getOutputs() const { return m_info[QStringLiteral("outputs")].toList(); } void ControlConfig::setOutputs(QVariantList outputsInfo) { m_info[QStringLiteral("outputs")] = outputsInfo; } -ControlOutput::ControlOutput(KScreen::OutputPtr output) - : m_output(output) +ControlOutput::ControlOutput(KScreen::OutputPtr output, QObject *parent) + : Control(parent) + , m_output(output) { } QString ControlOutput::dirPath() const { return Control::dirPath() % QStringLiteral("outputs/"); } QString ControlOutput::filePath() const { if (!m_output) { return QString(); } return filePathFromHash(m_output->hashMd5()); } diff --git a/common/control.h b/common/control.h index 3b29c6e..14ad374 100644 --- a/common/control.h +++ b/common/control.h @@ -1,91 +1,93 @@ /******************************************************************** Copyright 2019 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef COMMON_CONTROL_H #define COMMON_CONTROL_H #include #include #include -class Control +class Control : public QObject { - Q_GADGET + Q_OBJECT public: enum class OutputRetention { Undefined = -1, Global = 0, Individual = 1, }; Q_ENUM(OutputRetention) + explicit Control(QObject *parent = nullptr); - virtual ~Control() = default; + + ~Control() override = default; protected: virtual QString dirPath() const; virtual QString filePath() const = 0; QString filePathFromHash(const QString &hash) const; static OutputRetention convertVariantToOutputRetention(QVariant variant); private: static QString s_dirName; }; class ControlConfig : public Control { - Q_GADGET + Q_OBJECT public: - ControlConfig(KScreen::ConfigPtr config); + explicit ControlConfig(KScreen::ConfigPtr config, QObject *parent = nullptr); OutputRetention getOutputRetention(const KScreen::OutputPtr &output) const; OutputRetention getOutputRetention(const QString &outputId, const QString &outputName) const; void setOutputRetention(const KScreen::OutputPtr &output, OutputRetention value); void setOutputRetention(const QString &outputId, const QString &outputName, OutputRetention value); bool writeFile(); QString dirPath() const override; QString filePath() const override; private: QVariantList getOutputs() const; void setOutputs(QVariantList outputsInfo); bool infoIsOutput(const QVariantMap &info, const QString &outputId, const QString &outputName) const; KScreen::ConfigPtr m_config; QVariantMap m_info; QStringList m_duplicateOutputIds; }; class ControlOutput : public Control { - Q_GADGET + Q_OBJECT public: - ControlOutput(KScreen::OutputPtr output); + explicit ControlOutput(KScreen::OutputPtr output, QObject *parent = nullptr); // TODO: scale auto value QString dirPath() const override; QString filePath() const override; private: KScreen::OutputPtr m_output; }; #endif diff --git a/kded/config.cpp b/kded/config.cpp index e70ddd3..ed43595 100644 --- a/kded/config.cpp +++ b/kded/config.cpp @@ -1,249 +1,253 @@ /******************************************************************** Copyright 2012 Alejandro Fiestas Olivares Copyright 2019 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "config.h" #include "output.h" #include "../common/globals.h" #include "../common/control.h" #include "kscreen_daemon_debug.h" #include "device.h" #include #include #include #include #include #include #include QString Config::s_fixedConfigFileName = QStringLiteral("fixed-config"); QString Config::s_configsDirName = QStringLiteral("" /*"configs/"*/); // TODO: KDE6 - move these files into the subfolder QString Config::configsDirPath() { return Globals::dirPath() % s_configsDirName; } -Config::Config(KScreen::ConfigPtr config) - : m_data(config) +Config::Config(KScreen::ConfigPtr config, QObject *parent) + : QObject(parent) + , m_data(config) + , m_control(new ControlConfig(config, this)) { } QString Config::filePath() { if (!QDir().mkpath(configsDirPath())) { return QString(); } return configsDirPath() % id(); } QString Config::id() const { if (!m_data) { return QString(); } return m_data->connectedOutputsHash(); } bool Config::fileExists() const { return (QFile::exists(configsDirPath() % id()) || QFile::exists(configsDirPath() % s_fixedConfigFileName)); } std::unique_ptr Config::readFile() { if (Device::self()->isLaptop() && !Device::self()->isLidClosed()) { // We may look for a config that has been set when the lid was closed, Bug: 353029 const QString lidOpenedFilePath(filePath() % QStringLiteral("_lidOpened")); const QFile srcFile(lidOpenedFilePath); if (srcFile.exists()) { QFile::remove(filePath()); if (QFile::copy(lidOpenedFilePath, filePath())) { QFile::remove(lidOpenedFilePath); qCDebug(KSCREEN_KDED) << "Restored lid opened config to" << id(); } } } return readFile(id()); } std::unique_ptr Config::readOpenLidFile() { const QString openLidFile = id() % QStringLiteral("_lidOpened"); auto config = readFile(openLidFile); QFile::remove(configsDirPath() % openLidFile); return config; } std::unique_ptr Config::readFile(const QString &fileName) { if (!m_data) { return nullptr; } auto config = std::unique_ptr(new Config(m_data->clone())); config->setValidityFlags(m_validityFlags); QFile file; if (QFile::exists(configsDirPath() % s_fixedConfigFileName)) { file.setFileName(configsDirPath() % s_fixedConfigFileName); qCDebug(KSCREEN_KDED) << "found a fixed config, will use " << file.fileName(); } else { file.setFileName(configsDirPath() % fileName); } if (!file.open(QIODevice::ReadOnly)) { qCDebug(KSCREEN_KDED) << "failed to open file" << file.fileName(); return nullptr; } QJsonDocument parser; QVariantList outputs = parser.fromJson(file.readAll()).toVariant().toList(); Output::readInOutputs(config->data(), outputs); QSize screenSize; for (const auto &output : config->data()->outputs()) { if (!output->isPositionable()) { continue; } const QRect geom = output->geometry(); if (geom.x() + geom.width() > screenSize.width()) { screenSize.setWidth(geom.x() + geom.width()); } if (geom.y() + geom.height() > screenSize.height()) { screenSize.setHeight(geom.y() + geom.height()); } } config->data()->screen()->setCurrentSize(screenSize); if (!canBeApplied(config->data())) { return nullptr; } return config; } bool Config::canBeApplied() const { return canBeApplied(m_data); } bool Config::canBeApplied(KScreen::ConfigPtr config) const { #ifdef KDED_UNIT_TEST Q_UNUSED(config); return true; #else return KScreen::Config::canBeApplied(config, m_validityFlags); #endif } bool Config::writeFile() { return writeFile(filePath()); } bool Config::writeOpenLidFile() { return writeFile(filePath() % QStringLiteral("_lidOpened")); } bool Config::writeFile(const QString &filePath) { if (id().isEmpty()) { return false; } const KScreen::OutputList outputs = m_data->outputs(); - const auto control = ControlConfig(m_data); + // TODO: until we have the file watcher this is necessary to reload control files. + delete m_control; + m_control = new ControlConfig(m_data, this); const auto oldConfig = readFile(); KScreen::OutputList oldOutputs; if (oldConfig) { oldOutputs = oldConfig->data()->outputs(); } QVariantList outputList; for (const KScreen::OutputPtr &output : outputs) { QVariantMap info; const auto oldOutputIt = std::find_if(oldOutputs.constBegin(), oldOutputs.constEnd(), [output](const KScreen::OutputPtr &out) { return out->hashMd5() == output->hashMd5(); } ); const KScreen::OutputPtr oldOutput = oldOutputIt != oldOutputs.constEnd() ? *oldOutputIt : nullptr; if (!output->isConnected()) { continue; } Output::writeGlobalPart(output, info, oldOutput); info[QStringLiteral("primary")] = output->isPrimary(); info[QStringLiteral("enabled")] = output->isEnabled(); auto setOutputConfigInfo = [this, &info](const KScreen::OutputPtr &out) { if (!out) { return; } QString replicationSourceHash; if (int sourceId = out->replicationSource()) { replicationSourceHash = m_data->output(sourceId)->hashMd5(); } info[QStringLiteral("replicate")] = replicationSourceHash; QVariantMap pos; pos[QStringLiteral("x")] = out->pos().x(); pos[QStringLiteral("y")] = out->pos().y(); info[QStringLiteral("pos")] = pos; }; setOutputConfigInfo(output->isEnabled() ? output : oldOutput); if (output->isEnabled() && - control.getOutputRetention(output->hash(), output->name()) != + m_control->getOutputRetention(output->hash(), output->name()) != Control::OutputRetention::Individual) { // try to update global output data Output::writeGlobal(output); } outputList.append(info); } QFile file(filePath); if (!file.open(QIODevice::WriteOnly)) { qCWarning(KSCREEN_KDED) << "Failed to open config file for writing! " << file.errorString(); return false; } file.write(QJsonDocument::fromVariant(outputList).toJson()); qCDebug(KSCREEN_KDED) << "Config saved on: " << file.fileName(); return true; } void Config::log() { if (!m_data) { return; } const auto outputs = m_data->outputs(); for (const auto o : outputs) { if (o->isConnected()) { qCDebug(KSCREEN_KDED) << o; } } } diff --git a/kded/config.h b/kded/config.h index 227698b..5eb0c10 100644 --- a/kded/config.h +++ b/kded/config.h @@ -1,69 +1,73 @@ /******************************************************************** Copyright 2019 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KDED_CONFIG_H #define KDED_CONFIG_H #include #include -class Config +class ControlConfig; + +class Config : public QObject { + Q_OBJECT public: - explicit Config(KScreen::ConfigPtr config); + explicit Config(KScreen::ConfigPtr config, QObject *parent = nullptr); ~Config() = default; QString id() const; bool fileExists() const; std::unique_ptr readFile(); std::unique_ptr readOpenLidFile(); bool writeFile(); bool writeOpenLidFile(); KScreen::ConfigPtr data() const { return m_data; } void log(); void setValidityFlags(KScreen::Config::ValidityFlags flags) { m_validityFlags = flags; } bool canBeApplied() const; private: friend class TestConfig; QString filePath(); std::unique_ptr readFile(const QString &fileName); bool writeFile(const QString &filePath); bool canBeApplied(KScreen::ConfigPtr config) const; KScreen::ConfigPtr m_data; KScreen::Config::ValidityFlags m_validityFlags; + ControlConfig *m_control; static QString s_configsDirName; static QString s_fixedConfigFileName; static QString configsDirPath(); }; #endif