diff --git a/common/control.h b/common/control.h --- a/common/control.h +++ b/common/control.h @@ -25,14 +25,16 @@ { public: enum class OutputRetention { + Undefined = -1, Global = 0, - Individual + Individual = 1, }; virtual ~Control() = default; - virtual QString filePath() = 0; + static OutputRetention convertVariantToOutputRetention(QVariant variant); + protected: static QString dirPath(); @@ -45,7 +47,9 @@ public: ControlConfig(KScreen::ConfigPtr config); + 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(); diff --git a/common/control.cpp b/common/control.cpp --- a/common/control.cpp +++ b/common/control.cpp @@ -31,10 +31,24 @@ return Globals::dirPath() % s_dirName; } +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) { -// qDebug() << "Looking for control file:" << configId; +// qDebug() << "Looking for control file:" << config->connectedOutputsHash(); QFile file(filePath(config->connectedOutputsHash())); if (file.open(QIODevice::ReadOnly)) { // This might not be reached, bus this is ok. The control file will @@ -107,21 +121,22 @@ return true; } +Control::OutputRetention ControlConfig::getOutputRetention(const KScreen::OutputPtr &output) const +{ + return getOutputRetention(output->hash(), output->name()); +} + Control::OutputRetention ControlConfig::getOutputRetention(const QString &outputId, const QString &outputName) const { for (const auto variantInfo : m_info) { const QVariantMap info = variantInfo.toMap(); if (!infoIsOutput(info, outputId, outputName)) { continue; } - const auto retentionVariant = info[QStringLiteral("retention")]; - if (retentionVariant.canConvert() && retentionVariant.toInt() == (int)OutputRetention::Individual) { - return OutputRetention::Individual; - } - break; + return convertVariantToOutputRetention(info[QStringLiteral("retention")]); } - // default value - return OutputRetention::Global; + // info for output not found + return OutputRetention::Undefined; } static QVariantMap metadata(const QString &outputName) @@ -131,6 +146,11 @@ return metadata; } +void ControlConfig::setOutputRetention(const KScreen::OutputPtr &output, OutputRetention value) +{ + setOutputRetention(output->hash(), output->name(), value); +} + void ControlConfig::setOutputRetention(const QString &outputId, const QString &outputName, OutputRetention value) { QList::iterator it; diff --git a/kcm/src/CMakeLists.txt b/kcm/src/CMakeLists.txt --- a/kcm/src/CMakeLists.txt +++ b/kcm/src/CMakeLists.txt @@ -16,6 +16,8 @@ widget.cpp previewwidget.cpp scalingconfig.cpp + ../../common/globals.cpp + ../../common/control.cpp ) ecm_qt_declare_logging_category(kcm_kscreen_SRCS HEADER kcm_screen_debug.h IDENTIFIER KSCREEN_KCM CATEGORY_NAME kscreen.kcm) diff --git a/kcm/src/controlpanel.h b/kcm/src/controlpanel.h --- a/kcm/src/controlpanel.h +++ b/kcm/src/controlpanel.h @@ -22,6 +22,8 @@ #ifndef CONTROLPANEL_H #define CONTROLPANEL_H +#include "../../common/control.h" + #include #include @@ -35,6 +37,8 @@ class QSlider; class QComboBox; +#include + class ControlPanel : public QFrame { Q_OBJECT @@ -47,6 +51,8 @@ void setUnifiedOutput(const KScreen::OutputPtr &output); + void save(); + public Q_SLOTS: void activateOutput(const KScreen::OutputPtr &output); @@ -63,6 +69,8 @@ QVBoxLayout *mLayout; UnifiedOutputConfig *mUnifiedOutputCfg; + + std::unique_ptr mControlConfig = nullptr; }; #endif // CONTROLPANEL_H diff --git a/kcm/src/controlpanel.cpp b/kcm/src/controlpanel.cpp --- a/kcm/src/controlpanel.cpp +++ b/kcm/src/controlpanel.cpp @@ -53,6 +53,7 @@ } mConfig = config; + mControlConfig = std::unique_ptr(new ControlConfig(config)); connect(mConfig.data(), &KScreen::Config::outputAdded, this, &ControlPanel::addOutput); connect(mConfig.data(), &KScreen::Config::outputRemoved, @@ -68,10 +69,9 @@ OutputConfig *outputCfg = new OutputConfig(this); outputCfg->setVisible(false); outputCfg->setShowScaleOption(mConfig->supportedFeatures().testFlag(KScreen::Config::Feature::PerOutputScaling)); - outputCfg->setOutput(output); + outputCfg->setOutput(output, mControlConfig->getOutputRetention(output)); connect(outputCfg, &OutputConfig::changed, this, &ControlPanel::changed); - mLayout->addWidget(outputCfg); mOutputConfigs << outputCfg; } @@ -123,3 +123,21 @@ this, &ControlPanel::changed); } } + +void ControlPanel::save() +{ + if (!mControlConfig) { + return; + } + for (const auto outputConfig : mOutputConfigs) { + if (!outputConfig->hasChange()) { + continue; + } + mControlConfig->setOutputRetention(outputConfig->output(), outputConfig->applyRetention()); + } + + if (!mControlConfig->writeFile()) { + // TODO: error handling + } + // TODO: write output controls in the future +} diff --git a/kcm/src/kcm_kscreen.cpp b/kcm/src/kcm_kscreen.cpp --- a/kcm/src/kcm_kscreen.cpp +++ b/kcm/src/kcm_kscreen.cpp @@ -116,6 +116,7 @@ return; } + mKScreenWidget->saveControls(); const KScreen::ConfigPtr &config = mKScreenWidget->currentConfig(); bool atLeastOneEnabledOutput = false; diff --git a/kcm/src/outputconfig.h b/kcm/src/outputconfig.h --- a/kcm/src/outputconfig.h +++ b/kcm/src/outputconfig.h @@ -22,13 +22,16 @@ #ifndef OUTPUTCONFIG_H #define OUTPUTCONFIG_H -#include +#include "../../common/control.h" + #include #include +#include #include class QCheckBox; +class QGroupBox; class ResolutionSlider; class QLabel; @@ -42,13 +45,17 @@ explicit OutputConfig(const KScreen::OutputPtr &output, QWidget *parent = nullptr); ~OutputConfig() override; - virtual void setOutput(const KScreen::OutputPtr &output); + virtual void setOutput(const KScreen::OutputPtr &output, Control::OutputRetention retention = Control::OutputRetention::Undefined); KScreen::OutputPtr output() const; void setTitle(const QString &title); void setShowScaleOption(bool showScaleOption); bool showScaleOption() const; + bool hasChange() const; + + Control::OutputRetention applyRetention(); + protected Q_SLOTS: void slotResolutionChanged(const QSize &size); void slotRotationChanged(int index); @@ -70,6 +77,15 @@ QComboBox *mScale = nullptr; QComboBox *mRefreshRate = nullptr; bool mShowScaleOption = false; + + QGroupBox *mRetentionGroupBox = nullptr; + QRadioButton *mGlobalRetentionButton = nullptr; + QRadioButton *mIndividualRetentionButton = nullptr; + + Control::OutputRetention mRetention = Control::OutputRetention::Undefined; + + // TODO: we should do this instead with a separate config being watched + bool mChanged = false; }; #endif // OUTPUTCONFIG_H diff --git a/kcm/src/outputconfig.cpp b/kcm/src/outputconfig.cpp --- a/kcm/src/outputconfig.cpp +++ b/kcm/src/outputconfig.cpp @@ -105,6 +105,7 @@ this, [=](bool checked) { mOutput->setEnabled(checked); qCDebug(KSCREEN_KCM) << mOutput.data() << mOutput->name() << mOutput->isEnabled(); + mChanged = true; Q_EMIT changed(); }); formLayout->addRow(i18n("Display:"), mEnabled); @@ -149,11 +150,27 @@ slotResolutionChanged(mResolution->currentResolution()); connect(mRefreshRate, static_cast(&QComboBox::activated), this, &OutputConfig::slotRefreshRateChanged); + + + mRetentionGroupBox = new QGroupBox(i18n("Retention of values"), this); + mGlobalRetentionButton = new QRadioButton(i18n("Save as new global values for this display."), this); + mIndividualRetentionButton = new QRadioButton(i18n("Save values only for display in this specific configuration."), this); + mIndividualRetentionButton->setChecked(mRetention == Control::OutputRetention::Individual); + mGlobalRetentionButton->setChecked(!mIndividualRetentionButton->isChecked()); + + QVBoxLayout *vbox2 = new QVBoxLayout(mRetentionGroupBox); + vbox2->addWidget(mGlobalRetentionButton); + vbox2->addWidget(mIndividualRetentionButton); + mRetentionGroupBox->setLayout(vbox2); + + vbox->addWidget(mRetentionGroupBox); } -void OutputConfig::setOutput(const KScreen::OutputPtr &output) +void OutputConfig::setOutput(const KScreen::OutputPtr &output, Control::OutputRetention retention) { mOutput = output; + mRetention = retention; + initUi(); } @@ -201,7 +218,7 @@ mRefreshRate->setCurrentIndex(i + 1); } } - + mChanged = true; Q_EMIT changed(); } @@ -211,6 +228,7 @@ static_cast(mRotation->itemData(index).toInt()); mOutput->setRotation(rotation); + mChanged = true; Q_EMIT changed(); } @@ -227,13 +245,15 @@ } mOutput->setCurrentModeId(modeId); + mChanged = true; Q_EMIT changed(); } void OutputConfig::slotScaleChanged(int index) { auto scale = mScale->itemData(index).toInt(); mOutput->setScale(scale); + mChanged = true; Q_EMIT changed(); } @@ -249,3 +269,18 @@ { return mShowScaleOption; } + +Control::OutputRetention OutputConfig::applyRetention() +{ + if (mIndividualRetentionButton->isChecked()) { + mRetention = Control::OutputRetention::Individual; + } else { + mRetention = Control::OutputRetention::Global; + } + return mRetention; +} + +bool OutputConfig::hasChange() const +{ + return mChanged; +} diff --git a/kcm/src/unifiedoutputconfig.h b/kcm/src/unifiedoutputconfig.h --- a/kcm/src/unifiedoutputconfig.h +++ b/kcm/src/unifiedoutputconfig.h @@ -37,7 +37,7 @@ explicit UnifiedOutputConfig(const KScreen::ConfigPtr &config, QWidget *parent); ~UnifiedOutputConfig() override; - void setOutput(const KScreen::OutputPtr &output) override; + void setOutput(const KScreen::OutputPtr &output, Control::OutputRetention retention = Control::OutputRetention::Undefined) override; private Q_SLOTS: void slotResolutionChanged(const QSize &size); diff --git a/kcm/src/unifiedoutputconfig.cpp b/kcm/src/unifiedoutputconfig.cpp --- a/kcm/src/unifiedoutputconfig.cpp +++ b/kcm/src/unifiedoutputconfig.cpp @@ -61,8 +61,10 @@ { } -void UnifiedOutputConfig::setOutput(const KScreen::OutputPtr &output) +void UnifiedOutputConfig::setOutput(const KScreen::OutputPtr &output, Control::OutputRetention retention) { + Q_UNUSED(retention); // TODO: use? + mOutput = output; mClones.clear(); diff --git a/kcm/src/widget.h b/kcm/src/widget.h --- a/kcm/src/widget.h +++ b/kcm/src/widget.h @@ -32,6 +32,8 @@ class ControlPanel; class PrimaryOutputCombo; +class ControlConfig; + class QPushButton; class QComboBox; @@ -64,6 +66,7 @@ Q_SIGNALS: void changed(); + void saveControls(); private Q_SLOTS: void slotFocusedOutputChanged(QMLOutput *output); diff --git a/kcm/src/widget.cpp b/kcm/src/widget.cpp --- a/kcm/src/widget.cpp +++ b/kcm/src/widget.cpp @@ -33,6 +33,7 @@ #include "declarative/qmlscreen.h" #include "utils.h" #include "scalingconfig.h" +#include "../../common/control.h" #include #include @@ -92,6 +93,7 @@ dialog->exec(); delete dialog; }); + connect(this, &Widget::saveControls, mControlPanel, &ControlPanel::save); mOutputTimer = new QTimer(this); connect(mOutputTimer, &QTimer::timeout, diff --git a/kded/config.cpp b/kded/config.cpp --- a/kded/config.cpp +++ b/kded/config.cpp @@ -114,7 +114,7 @@ QJsonDocument parser; QVariantList outputs = parser.fromJson(file.readAll()).toVariant().toList(); - Output::readInOutputs(config->data()->outputs(), outputs); + Output::readInOutputs(config->data(), outputs); QSize screenSize; for (const auto &output : config->data()->outputs()) { @@ -175,7 +175,6 @@ QVariantList outputList; Q_FOREACH(const KScreen::OutputPtr &output, outputs) { QVariantMap info; - if (!output->isConnected()) { continue; } diff --git a/kded/output.h b/kded/output.h --- a/kded/output.h +++ b/kded/output.h @@ -26,7 +26,7 @@ class Output { public: - static void readInOutputs(KScreen::OutputList outputs, const QVariantList &outputsInfo); + static void readInOutputs(KScreen::ConfigPtr config, const QVariantList &outputsInfo); static bool writeGlobal(const KScreen::OutputPtr &output); static bool writeGlobalPart(const KScreen::OutputPtr &output, QVariantMap &info); diff --git a/kded/output.cpp b/kded/output.cpp --- a/kded/output.cpp +++ b/kded/output.cpp @@ -49,7 +49,7 @@ void Output::readInPart(KScreen::OutputPtr output, const QVariantMap &info) { - output->setRotation(static_cast(info[QStringLiteral("rotation")].toInt())); + output->setRotation(static_cast(info.value(QStringLiteral("rotation"), 1).toInt())); output->setScale(info.value(QStringLiteral("scale"), 1).toInt()); const QVariantMap modeInfo = info[QStringLiteral("mode")].toMap(); @@ -117,17 +117,19 @@ output->setPrimary(info[QStringLiteral("primary")].toBool()); output->setEnabled(info[QStringLiteral("enabled")].toBool()); - if (retention == Control::OutputRetention::Global) { + if (retention == Control::OutputRetention::Undefined || retention == Control::OutputRetention::Global) { // output data read from global output file readInGlobal(output); } else { // output data read directly from info readInPart(output, info); } } -void Output::readInOutputs(KScreen::OutputList outputs, const QVariantList &outputsInfo) +void Output::readInOutputs(KScreen::ConfigPtr config, const QVariantList &outputsInfo) { + KScreen::OutputList outputs = config->outputs(); + ControlConfig control(config); // 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 duplicateIds; @@ -167,14 +169,7 @@ } } infoFound = true; - - Control::OutputRetention retention = Control::OutputRetention::Global; - const auto retentionVariant = info[QStringLiteral("retention")]; - if (retentionVariant.canConvert() && retentionVariant.toInt() == (int)Control::OutputRetention::Individual) { - retention = Control::OutputRetention::Individual; - } - - readIn(output, info, retention); + readIn(output, info, control.getOutputRetention(output)); break; } if (!infoFound) { diff --git a/tests/kded/configtest.cpp b/tests/kded/configtest.cpp --- a/tests/kded/configtest.cpp +++ b/tests/kded/configtest.cpp @@ -34,6 +34,7 @@ Q_OBJECT private Q_SLOTS: + void init(); void initTestCase(); void testSimpleConfig(); @@ -101,10 +102,14 @@ return configWrapper; } +void TestConfig::init() +{ + Globals::setDirPath(QStringLiteral(TEST_DATA "serializerdata/")); +} + void TestConfig::initTestCase() { qputenv("KSCREEN_LOGGING", "false"); - Globals::setDirPath(QStringLiteral(TEST_DATA "serializerdata/")); } void TestConfig::testSimpleConfig() @@ -481,15 +486,13 @@ QCOMPARE(output2->name(), QLatin1String("OUTPUT-2")); QCOMPARE(output2->isEnabled(), true); QCOMPARE(output2->isPrimary(), false); - - Globals::setDirPath(QStringLiteral(TEST_DATA "/serializerdata/")); } void TestConfig::testFixedConfig() { // Load a dualhead config auto configWrapper = createConfig(true, true); - configWrapper = std::move(configWrapper->readFile(QStringLiteral("twoScreenConfig.json"))); + configWrapper = configWrapper->readFile(QStringLiteral("twoScreenConfig.json")); auto config = configWrapper->data(); QVERIFY(config); @@ -505,8 +508,6 @@ // Check if both files exist QFile fixedCfg(fixedCfgPath); QVERIFY(fixedCfg.exists()); - - Globals::setDirPath(QStringLiteral(TEST_DATA "/serializerdata/")); } diff --git a/tests/kded/serializerdata/control/configs/e919cc0dd7aea8d8f519bdf8b93a6f69 b/tests/kded/serializerdata/control/configs/e919cc0dd7aea8d8f519bdf8b93a6f69 --- a/tests/kded/serializerdata/control/configs/e919cc0dd7aea8d8f519bdf8b93a6f69 +++ b/tests/kded/serializerdata/control/configs/e919cc0dd7aea8d8f519bdf8b93a6f69 @@ -1,26 +1,44 @@ [ { "retention" : 1, - "id" : "be55eeb5fcc1e775f321c1ae3aa02ef0" + "id" : "be55eeb5fcc1e775f321c1ae3aa02ef0", + "metadata" : { + "name" : "DisplayPort-0" + } }, { "retention" : 1, - "id" : "be55eeb5fcc1e775f321c1ae3aa02ef0" + "id" : "be55eeb5fcc1e775f321c1ae3aa02ef0", + "metadata" : { + "name" : "DisplayPort-1" + } }, { "retention" : 1, - "id" : "be55eeb5fcc1e775f321c1ae3aa02ef0" + "id" : "be55eeb5fcc1e775f321c1ae3aa02ef0", + "metadata" : { + "name" : "DisplayPort-2" + } }, { "retention" : 1, - "id" : "be55eeb5fcc1e775f321c1ae3aa02ef0" + "id" : "be55eeb5fcc1e775f321c1ae3aa02ef0", + "metadata" : { + "name" : "DisplayPort-3" + } }, { "retention" : 1, - "id" : "be55eeb5fcc1e775f321c1ae3aa02ef0" + "id" : "be55eeb5fcc1e775f321c1ae3aa02ef0", + "metadata" : { + "name" : "DVI-0" + } }, { "retention" : 1, - "id" : "be55eeb5fcc1e775f321c1ae3aa02ef0" + "id" : "be55eeb5fcc1e775f321c1ae3aa02ef0", + "metadata" : { + "name" : "DVI-1" + } } ]