diff --git a/kded/CMakeLists.txt b/kded/CMakeLists.txt --- a/kded/CMakeLists.txt +++ b/kded/CMakeLists.txt @@ -6,6 +6,7 @@ daemon.cpp config.cpp output.cpp + control.cpp generator.cpp device.cpp osd.cpp diff --git a/kded/config.cpp b/kded/config.cpp --- a/kded/config.cpp +++ b/kded/config.cpp @@ -17,6 +17,7 @@ *********************************************************************/ #include "config.h" #include "output.h" +#include "control.h" #include "kscreen_daemon_debug.h" #include "device.h" @@ -104,7 +105,8 @@ if (!m_data) { return nullptr; } - KScreen::ConfigPtr config = m_data->clone(); + auto config = std::unique_ptr(new Config(m_data->clone())); + config->setValidityFlags(m_validityFlags); QFile file; if (QFile::exists(configsDirPath() % s_fixedConfigFileName)) { @@ -120,10 +122,10 @@ QJsonDocument parser; QVariantList outputs = parser.fromJson(file.readAll()).toVariant().toList(); - Output::readInOutputs(config->outputs(), outputs); + Output::readInOutputs(config->data()->outputs(), outputs, Control::readInOutputRetentionValues(config->id())); QSize screenSize; - for (const auto &output : config->outputs()) { + for (const auto &output : config->data()->outputs()) { if (!output->isConnected() || !output->isEnabled()) { continue; } @@ -136,14 +138,12 @@ screenSize.setHeight(geom.y() + geom.height()); } } - config->screen()->setCurrentSize(screenSize); + config->data()->screen()->setCurrentSize(screenSize); - if (!canBeApplied(config)) { + if (!canBeApplied(config->data())) { return nullptr; } - auto cfg = std::unique_ptr(new Config(config)); - cfg->setValidityFlags(m_validityFlags); - return cfg; + return config; } bool Config::canBeApplied() const @@ -173,11 +173,13 @@ bool Config::writeFile(const QString &filePath) { - if (!m_data) { + if (id().isEmpty()) { return false; } const KScreen::OutputList outputs = m_data->outputs(); + const auto retentions = Control::readInOutputRetentionValues(id()); + QVariantList outputList; Q_FOREACH(const KScreen::OutputPtr &output, outputs) { QVariantMap info; @@ -197,8 +199,10 @@ pos[QStringLiteral("y")] = output->pos().y(); info[QStringLiteral("pos")] = pos; - // try to update global output data - Output::writeGlobal(output); + if (Control::getOutputRetention(output->hash(), retentions) != Control::OutputRetention::Individual) { + // try to update global output data + Output::writeGlobal(output); + } outputList.append(info); } diff --git a/kded/output.h b/kded/control.h copy from kded/output.h copy to kded/control.h --- a/kded/output.h +++ b/kded/control.h @@ -14,30 +14,31 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ -#ifndef KDED_OUTPUT_H -#define KDED_OUTPUT_H +#ifndef KDED_CONTROL_H +#define KDED_CONTROL_H #include #include -class Output +class Control { public: - static void readInOutputs(KScreen::OutputList outputs, const QVariantList &outputsInfo); + enum class OutputRetention { + Undefined = -1, + Global = 0, + Individual = 1, + }; - static void writeGlobal(const KScreen::OutputPtr &output); - static bool writeGlobalPart(const KScreen::OutputPtr &output, QVariantMap &info); + static QMap readInOutputRetentionValues(const QString &configId); + static OutputRetention getOutputRetention(const QString &outputId, const QMap &retentions); - static QString dirPath(); + static QString configFilePath(const QString &hash); + static QString outputFilePath(const QString &hash); private: - static QString globalFileName(const QString &hash); - static QVariantMap getGlobalData(KScreen::OutputPtr output); - - static void readIn(KScreen::OutputPtr output, const QVariantMap &info); - static bool readInGlobal(KScreen::OutputPtr output); - static void readInGlobalPartFromInfo(KScreen::OutputPtr output, const QVariantMap &info); + static QString dirPath(); + static OutputRetention convertVariantToOutputRetention(QVariant variant); static QString s_dirName; }; diff --git a/kded/control.cpp b/kded/control.cpp new file mode 100644 --- /dev/null +++ b/kded/control.cpp @@ -0,0 +1,107 @@ +/******************************************************************** +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 "config.h" + +#include "kscreen_daemon_debug.h" +#include "generator.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +QString Control::s_dirName = QStringLiteral("control/"); + +QString Control::dirPath() +{ + return Config::dirPath() % s_dirName; +} + +QString Control::outputFilePath(const QString &hash) +{ + const QString dir = dirPath() % QStringLiteral("outputs/"); + if (!QDir().mkpath(dir)) { + return QString(); + } + return dir % hash; +} + +QString Control::configFilePath(const QString &hash) +{ + const QString dir = dirPath() % QStringLiteral("configs/"); + if (!QDir().mkpath(dir)) { + return QString(); + } + return dir % 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; +} + +QMap Control::readInOutputRetentionValues(const QString &configId) +{ +// qDebug() << "Looking for control file:" << configId; + QFile file(configFilePath(configId)); + if (!file.open(QIODevice::ReadOnly)) { + qCDebug(KSCREEN_KDED) << "Failed to open file" << file.fileName(); + return QMap(); + } + + QJsonDocument parser; + const QVariantMap controlInfo = parser.fromJson(file.readAll()).toVariant().toMap(); + const QVariantList outputsInfo = controlInfo[QStringLiteral("outputs")].toList(); + QMap retentions; + + for (const auto variantInfo : outputsInfo) { + const QVariantMap info = variantInfo.toMap(); + + // TODO: this does not yet consider the output name (i.e. connector). Necessary? + const QString outputHash = info[QStringLiteral("id")].toString(); + if (outputHash.isEmpty()) { + continue; + } + retentions[outputHash] = convertVariantToOutputRetention(info[QStringLiteral("retention")]); + } + + return retentions; +} + +Control::OutputRetention Control::getOutputRetention(const QString &outputId, const QMap &retentions) +{ + if (retentions.contains(outputId)) { + return retentions[outputId]; + } + // info for output not found + return OutputRetention::Undefined; +} diff --git a/kded/output.h b/kded/output.h --- a/kded/output.h +++ b/kded/output.h @@ -17,14 +17,16 @@ #ifndef KDED_OUTPUT_H #define KDED_OUTPUT_H +#include "control.h" + #include #include class Output { public: - static void readInOutputs(KScreen::OutputList outputs, const QVariantList &outputsInfo); + static void readInOutputs(KScreen::OutputList outputs, const QVariantList &outputsInfo, const QMap &retentions); static void writeGlobal(const KScreen::OutputPtr &output); static bool writeGlobalPart(const KScreen::OutputPtr &output, QVariantMap &info); @@ -35,7 +37,7 @@ static QString globalFileName(const QString &hash); static QVariantMap getGlobalData(KScreen::OutputPtr output); - static void readIn(KScreen::OutputPtr output, const QVariantMap &info); + static void readIn(KScreen::OutputPtr output, const QVariantMap &info, Control::OutputRetention retention); static bool readInGlobal(KScreen::OutputPtr output); static void readInGlobalPartFromInfo(KScreen::OutputPtr output, const QVariantMap &info); diff --git a/kded/output.cpp b/kded/output.cpp --- a/kded/output.cpp +++ b/kded/output.cpp @@ -113,21 +113,23 @@ return true; } -void Output::readIn(KScreen::OutputPtr output, const QVariantMap &info) +void Output::readIn(KScreen::OutputPtr output, const QVariantMap &info, Control::OutputRetention retention) { const QVariantMap posInfo = info[QStringLiteral("pos")].toMap(); QPoint point(posInfo[QStringLiteral("x")].toInt(), posInfo[QStringLiteral("y")].toInt()); output->setPos(point); output->setPrimary(info[QStringLiteral("primary")].toBool()); output->setEnabled(info[QStringLiteral("enabled")].toBool()); - if (!readInGlobal(output)) { - // read in global part from config info instead - readInGlobalPartFromInfo(output, info); + if (retention != Control::OutputRetention::Individual && readInGlobal(output)) { + // output data read from global output file + return; } + // output data read directly from info + readInGlobalPartFromInfo(output, info); } -void Output::readInOutputs(KScreen::OutputList outputs, const QVariantList &outputsInfo) +void Output::readInOutputs(KScreen::OutputList outputs, const QVariantList &outputsInfo, const QMap &retentions) { // 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 @@ -151,26 +153,27 @@ continue; } const auto outputId = output->hash(); + const auto retention = Control::getOutputRetention(outputId, retentions); bool infoFound = false; for (const auto &variantInfo : outputsInfo) { const QVariantMap info = variantInfo.toMap(); - if (outputId == info[QStringLiteral("id")].toString()) { - + if (outputId != info[QStringLiteral("id")].toString()) { + continue; + } + if (!output->name().isEmpty() && duplicateIds.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) - if (!output->name().isEmpty() && duplicateIds.contains(outputId)) { - const auto metadata = info[QStringLiteral("metadata")].toMap(); - const auto outputName = metadata[QStringLiteral("name")].toString(); - if (output->name() != outputName) { - infoFound = true; - readIn(output, info); - } + const auto metadata = info[QStringLiteral("metadata")].toMap(); + const auto outputName = metadata[QStringLiteral("name")].toString(); + if (output->name() != outputName) { // was a duplicate id, but info not for this output continue; } - infoFound = true; - readIn(output, info); } + + infoFound = true; + readIn(output, info, retention); + break; } if (!infoFound) { // no info in info for this output, try reading in global output info atleast or set some default values diff --git a/tests/kded/CMakeLists.txt b/tests/kded/CMakeLists.txt --- a/tests/kded/CMakeLists.txt +++ b/tests/kded/CMakeLists.txt @@ -9,6 +9,7 @@ ${CMAKE_SOURCE_DIR}/kded/device.cpp ${CMAKE_SOURCE_DIR}/kded/config.cpp ${CMAKE_SOURCE_DIR}/kded/output.cpp + ${CMAKE_SOURCE_DIR}/kded/control.cpp #${CMAKE_SOURCE_DIR}/kded/daemon.cpp ) ecm_qt_declare_logging_category(test_SRCS HEADER kscreen_daemon_debug.h IDENTIFIER KSCREEN_KDED CATEGORY_NAME kscreen.kded) diff --git a/tests/kded/configtest.cpp b/tests/kded/configtest.cpp --- a/tests/kded/configtest.cpp +++ b/tests/kded/configtest.cpp @@ -408,6 +408,7 @@ // Make sure we don't write into TEST_DATA QStandardPaths::setTestModeEnabled(true); configWrapper->setDirPath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) % QStringLiteral("/kscreen/")); + // TODO: this needs setup of the control directory // Basic assumptions for the remainder of our tests, this is the situation where the lid is opened QCOMPARE(config->connectedOutputs().count(), 2); @@ -490,12 +491,13 @@ auto configWrapper = createConfig(true, true); configWrapper = std::move(configWrapper->readFile(QStringLiteral("twoScreenConfig.json"))); auto config = configWrapper->data(); - QVERIFY(config); // Make sure we don't write into TEST_DATA QStandardPaths::setTestModeEnabled(true); Config::setDirPath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) % QStringLiteral("/kscreen/")); + // TODO: this needs setup of the control directory + const QString fixedCfgPath = Config::configsDirPath() % Config::s_fixedConfigFileName; // save config as the current one, this is the config we don't want restored, and which we'll overwrite configWrapper->writeFile(fixedCfgPath); diff --git a/tests/kded/serializerdata/control/configs/229fa9e8a378cb151d7302df42666774 b/tests/kded/serializerdata/control/configs/229fa9e8a378cb151d7302df42666774 new file mode 100644 --- /dev/null +++ b/tests/kded/serializerdata/control/configs/229fa9e8a378cb151d7302df42666774 @@ -0,0 +1,8 @@ +{ + "outputs": [ + { + "retention": 1, + "id": "OUTPUT-1" + } + ] +} diff --git a/tests/kded/serializerdata/control/configs/8684e883209d7644eb76feea2081c431 b/tests/kded/serializerdata/control/configs/8684e883209d7644eb76feea2081c431 new file mode 100644 --- /dev/null +++ b/tests/kded/serializerdata/control/configs/8684e883209d7644eb76feea2081c431 @@ -0,0 +1,12 @@ +{ + "outputs": [ + { + "retention": 1, + "id": "OUTPUT-1" + }, + { + "retention": 1, + "id": "OUTPUT-2" + } + ] +} diff --git a/tests/kded/serializerdata/control/configs/e919cc0dd7aea8d8f519bdf8b93a6f69 b/tests/kded/serializerdata/control/configs/e919cc0dd7aea8d8f519bdf8b93a6f69 new file mode 100644 --- /dev/null +++ b/tests/kded/serializerdata/control/configs/e919cc0dd7aea8d8f519bdf8b93a6f69 @@ -0,0 +1,28 @@ +{ + "outputs": [ + { + "retention": 1, + "id": "be55eeb5fcc1e775f321c1ae3aa02ef0" + }, + { + "retention": 1, + "id": "be55eeb5fcc1e775f321c1ae3aa02ef0" + }, + { + "retention": 1, + "id": "be55eeb5fcc1e775f321c1ae3aa02ef0" + }, + { + "retention": 1, + "id": "be55eeb5fcc1e775f321c1ae3aa02ef0" + }, + { + "retention": 1, + "id": "be55eeb5fcc1e775f321c1ae3aa02ef0" + }, + { + "retention": 1, + "id": "be55eeb5fcc1e775f321c1ae3aa02ef0" + } + ] +}