diff --git a/common/control.cpp b/common/control.cpp index 90240e8..7f3e75f 100644 --- a/common/control.cpp +++ b/common/control.cpp @@ -1,219 +1,224 @@ /******************************************************************** 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/"); QString Control::dirPath() { 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:" << 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 // 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->hash(); 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::filePath(const QString &hash) { const QString dir = dirPath() % QStringLiteral("configs/"); if (!QDir().mkpath(dir)) { return QString(); } return dir % hash; } QString ControlConfig::filePath() { if (!m_config) { return QString(); } return ControlConfig::filePath(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->hash(), 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->hash(), 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() { // write updated data to file QFile file(filePath()); 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) { } QString ControlOutput::filePath(const QString &hash) { const QString dir = dirPath() % QStringLiteral("outputs/"); if (!QDir().mkpath(dir)) { return QString(); } return dir % hash; } QString ControlOutput::filePath() { if (!m_output) { return QString(); } return ControlOutput::filePath(m_output->hash()); } diff --git a/common/control.h b/common/control.h index 1856155..63b2f69 100644 --- a/common/control.h +++ b/common/control.h @@ -1,83 +1,84 @@ /******************************************************************** 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 class Control { public: enum class OutputRetention { Undefined = -1, Global = 0, Individual = 1, }; virtual ~Control() = default; virtual QString filePath() = 0; protected: static QString dirPath(); static OutputRetention convertVariantToOutputRetention(QVariant variant); private: static QString s_dirName; }; class ControlConfig : public Control { 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(); QString filePath() override; static QString filePath(const QString &hash); 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 { public: ControlOutput(KScreen::OutputPtr output); // TODO: scale auto value QString filePath() override; static QString filePath(const QString &hash); private: KScreen::OutputPtr m_output; }; #endif diff --git a/kcm/src/CMakeLists.txt b/kcm/src/CMakeLists.txt index 6d4f91b..ebb17f4 100644 --- a/kcm/src/CMakeLists.txt +++ b/kcm/src/CMakeLists.txt @@ -1,50 +1,52 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kcm_displayconfiguration\") include_directories("${CMAKE_CURRENT_SOURCE_DIR}/declarative") set(kcm_kscreen_SRCS declarative/qmloutput.cpp declarative/qmloutputcomponent.cpp declarative/qmlscreen.cpp controlpanel.cpp outputconfig.cpp unifiedoutputconfig.cpp resolutionslider.cpp utils.cpp 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) ki18n_wrap_ui(kcm_kscreen_SRCS kscreen_widget.ui stylepreview.ui scaling.ui) add_library(kcm_kscreen MODULE kcm_kscreen.cpp ${kcm_kscreen_SRCS}) target_link_libraries(kcm_kscreen Qt5::QuickWidgets Qt5::Widgets KF5::Screen KF5::I18n KF5::ConfigCore KF5::ConfigWidgets KF5::WidgetsAddons ) install(TARGETS kcm_kscreen DESTINATION ${KDE_INSTALL_PLUGINDIR} ) add_executable(kcm_testapp kcm_testapp.cpp ${kcm_kscreen_SRCS}) set_target_properties(kcm_testapp PROPERTIES COMPILE_FLAGS "-DQT_DECLARATIVE_DEBUG") target_link_libraries(kcm_testapp Qt5::QuickWidgets Qt5::Widgets KF5::CoreAddons KF5::I18n KF5::ConfigCore KF5::Screen KF5::WidgetsAddons ) diff --git a/kcm/src/controlpanel.cpp b/kcm/src/controlpanel.cpp index 8dc2ead..b54ff48 100644 --- a/kcm/src/controlpanel.cpp +++ b/kcm/src/controlpanel.cpp @@ -1,125 +1,143 @@ /* * Copyright 2013 Daniel Vrátil * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 "controlpanel.h" #include "outputconfig.h" #include "unifiedoutputconfig.h" #include "kcm_screen_debug.h" #include #include ControlPanel::ControlPanel(QWidget *parent) : QFrame(parent) , mUnifiedOutputCfg(nullptr) { setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); mLayout = new QVBoxLayout(this); } ControlPanel::~ControlPanel() { } void ControlPanel::setConfig(const KScreen::ConfigPtr &config) { qDeleteAll(mOutputConfigs); mOutputConfigs.clear(); delete mUnifiedOutputCfg; mUnifiedOutputCfg = nullptr; if (mConfig) { mConfig->disconnect(this); } mConfig = config; + mControlConfig = std::unique_ptr(new ControlConfig(config)); connect(mConfig.data(), &KScreen::Config::outputAdded, this, &ControlPanel::addOutput); connect(mConfig.data(), &KScreen::Config::outputRemoved, this, &ControlPanel::removeOutput); for (const KScreen::OutputPtr &output : mConfig->outputs()) { addOutput(output); } } void ControlPanel::addOutput(const KScreen::OutputPtr &output) { 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; } void ControlPanel::removeOutput(int outputId) { for (OutputConfig *outputCfg : mOutputConfigs) { if (outputCfg->output()->id() == outputId) { mOutputConfigs.removeOne(outputCfg); delete outputCfg; return; } } } void ControlPanel::activateOutput(const KScreen::OutputPtr &output) { // Ignore activateOutput when in unified mode if (mUnifiedOutputCfg) { return; } qCDebug(KSCREEN_KCM) << "Activate output" << output->id(); Q_FOREACH (OutputConfig *cfg, mOutputConfigs) { cfg->setVisible(cfg->output()->id() == output->id()); } } void ControlPanel::setUnifiedOutput(const KScreen::OutputPtr &output) { Q_FOREACH (OutputConfig *config, mOutputConfigs) { if (!config->output()->isConnected()) { continue; } config->setVisible(output == nullptr); } if (output.isNull()) { mUnifiedOutputCfg->deleteLater(); mUnifiedOutputCfg = nullptr; } else { mUnifiedOutputCfg = new UnifiedOutputConfig(mConfig, this); mUnifiedOutputCfg->setOutput(output); mUnifiedOutputCfg->setVisible(true); mLayout->insertWidget(mLayout->count() - 2, mUnifiedOutputCfg); connect(mUnifiedOutputCfg, &UnifiedOutputConfig::changed, 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/controlpanel.h b/kcm/src/controlpanel.h index f39e0f2..e6a8044 100644 --- a/kcm/src/controlpanel.h +++ b/kcm/src/controlpanel.h @@ -1,64 +1,72 @@ /* * Copyright 2013 Daniel Vrátil * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 CONTROLPANEL_H #define CONTROLPANEL_H +#include "../../common/control.h" + #include #include class QVBoxLayout; class OutputConfig; class UnifiedOutputConfig; +#include + class ControlPanel : public QFrame { Q_OBJECT public: explicit ControlPanel(QWidget *parent = nullptr); ~ControlPanel() override; void setConfig(const KScreen::ConfigPtr &config); void setUnifiedOutput(const KScreen::OutputPtr &output); + void save(); + public Q_SLOTS: void activateOutput(const KScreen::OutputPtr &output); Q_SIGNALS: void changed(); private Q_SLOTS: void addOutput(const KScreen::OutputPtr &output); void removeOutput(int outputId); private: KScreen::ConfigPtr mConfig; QList mOutputConfigs; QVBoxLayout *mLayout; UnifiedOutputConfig *mUnifiedOutputCfg; + + std::unique_ptr mControlConfig = nullptr; }; #endif // CONTROLPANEL_H diff --git a/kcm/src/kcm_kscreen.cpp b/kcm/src/kcm_kscreen.cpp index 91b97cb..4e8766e 100644 --- a/kcm/src/kcm_kscreen.cpp +++ b/kcm/src/kcm_kscreen.cpp @@ -1,188 +1,189 @@ /* Copyright (C) 2012 Dan Vratil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "kcm_kscreen.h" #include "kcm_screen_debug.h" #include "widget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY(KCMDisplayConfigurationFactory, registerPlugin();) using namespace KScreen; Q_DECLARE_METATYPE(KScreen::OutputPtr) Q_DECLARE_METATYPE(KScreen::ScreenPtr) KCMKScreen::KCMKScreen(QWidget* parent, const QVariantList& args) : KCModule(parent, args) { Log::instance(); setButtons(Apply | Default); KAboutData* about = new KAboutData(QStringLiteral("kcm_kscreen"), i18n("Display Configuration"), QStringLiteral(KSCREEN_VERSION), i18n("Configuration for displays"), KAboutLicense::GPL_V2, i18n("(c), 2012-2013 Daniel Vrátil")); about->addAuthor(i18n("Daniel Vrátil"), i18n("Maintainer") , QStringLiteral("dvratil@redhat.com")); setAboutData(about); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); } void KCMKScreen::configReady(ConfigOperation* op) { delete mMainLayout; mMainLayout = new QHBoxLayout(this); mMainLayout->setContentsMargins(0, 0, 0, 0); if (op->hasError()) { mKScreenWidget = nullptr; delete mKScreenWidget; QLabel *errorLabel = new QLabel(this); mMainLayout->addWidget(errorLabel); errorLabel->setText(i18n("No kscreen backend found. Please check your kscreen installation.")); return; } if (!mKScreenWidget) { mKScreenWidget = new Widget(this); mMainLayout->addWidget(mKScreenWidget); QObject::connect(mKScreenWidget, &Widget::changed, this, &KCMKScreen::changed); } else { mMainLayout->addWidget(mKScreenWidget); } mKScreenWidget->setConfig(qobject_cast(op)->config()); } KCMKScreen::~KCMKScreen() { } QSize KCMKScreen::sizeHint() const { return QSize(0, 700); } void KCMKScreen::changed() { if (!m_blockChanges) { KCModule::changed(); } } void KCMKScreen::save() { qCDebug(KSCREEN_KCM) << "Saving."; if (!mKScreenWidget) { return; } + mKScreenWidget->saveControls(); const KScreen::ConfigPtr &config = mKScreenWidget->currentConfig(); bool atLeastOneEnabledOutput = false; Q_FOREACH(const KScreen::OutputPtr &output, config->outputs()) { KScreen::ModePtr mode = output->currentMode(); if (output->isEnabled()) { atLeastOneEnabledOutput = true; } qCDebug(KSCREEN_KCM) << output->name() << output->id() << output.data() << "\n" << " Connected:" << output->isConnected() << "\n" << " Enabled:" << output->isEnabled() << "\n" << " Primary:" << output->isPrimary() << "\n" << " Rotation:" << output->rotation() << "\n" << " Mode:" << (mode ? mode->name() : QStringLiteral("unknown")) << "@" << (mode ? mode->refreshRate() : 0.0) << "Hz" << "\n" << " Position:" << output->pos().x() << "x" << output->pos().y(); } if (!atLeastOneEnabledOutput) { if (KMessageBox::warningYesNo(this, i18n("Are you sure you want to disable all outputs?"), i18nc("@title:window", "Disable All Outputs"), KGuiItem(i18n("&Disable All Outputs"), QIcon::fromTheme(QStringLiteral("dialog-ok-apply"))), KGuiItem(i18n("&Reconfigure"), QIcon::fromTheme(QStringLiteral("dialog-cancel"))), QString(), KMessageBox::Dangerous) == KMessageBox::No) { return; } } if (!Config::canBeApplied(config)) { KMessageBox::information(this, i18n("Sorry, your configuration could not be applied.\n\n" "Common reasons are that the overall screen size is too big, or you enabled more displays than supported by your GPU."), i18nc("@title:window", "Unsupported Configuration")); return; } m_blockChanges = true; /* Store the current config, apply settings */ auto *op = new SetConfigOperation(config); /* Block until the operation is completed, otherwise KCMShell will terminate * before we get to execute the Operation */ op->exec(); // The 1000ms is a bit "random" here, it's what works on the systems I've tested, but ultimately, this is a hack // due to the fact that we just can't be sure when xrandr is done changing things, 1000 doesn't seem to get in the way QTimer::singleShot(1000, this, [this] () { m_blockChanges = false; } ); } void KCMKScreen::defaults() { qCDebug(KSCREEN_KCM) << "APPLY DEFAULT"; load(); } void KCMKScreen::load() { qCDebug(KSCREEN_KCM) << "LOAD"; connect(new GetConfigOperation(), &GetConfigOperation::finished, this, &KCMKScreen::configReady); } #include "kcm_kscreen.moc" diff --git a/kcm/src/outputconfig.cpp b/kcm/src/outputconfig.cpp index e8d64cc..6264310 100644 --- a/kcm/src/outputconfig.cpp +++ b/kcm/src/outputconfig.cpp @@ -1,251 +1,286 @@ /* * Copyright 2013 Daniel Vrátil * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 "outputconfig.h" #include "resolutionslider.h" #include "utils.h" #include "kcm_screen_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include OutputConfig::OutputConfig(QWidget *parent) : QWidget(parent) , mOutput(nullptr) { } OutputConfig::OutputConfig(const KScreen::OutputPtr &output, QWidget *parent) : QWidget(parent) { setOutput(output); } OutputConfig::~OutputConfig() { } void OutputConfig::setTitle(const QString& title) { mTitle->setText(title); } void OutputConfig::initUi() { connect(mOutput.data(), &KScreen::Output::isConnectedChanged, this, [=]() { if (!mOutput->isConnected()) { setVisible(false); } }); connect(mOutput.data(), &KScreen::Output::isEnabledChanged, this, [=]() { mEnabled->setChecked(mOutput->isEnabled()); }); connect(mOutput.data(), &KScreen::Output::rotationChanged, this, [=]() { const int index = mRotation->findData(mOutput->rotation()); mRotation->setCurrentIndex(index); }); connect(mOutput.data(), &KScreen::Output::scaleChanged, this, [=]() { const int index = mScale->findData(mOutput->scale()); mScale->setCurrentIndex(index); }); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); QVBoxLayout *vbox = new QVBoxLayout(this); mTitle = new QLabel(this); mTitle->setAlignment(Qt::AlignHCenter); vbox->addWidget(mTitle); setTitle(Utils::outputName(mOutput)); QFormLayout *formLayout = new QFormLayout(); vbox->addLayout(formLayout); mEnabled = new QCheckBox(i18n("Enabled"), this); mEnabled->setChecked(mOutput->isEnabled()); connect(mEnabled, &QCheckBox::clicked, 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); mResolution = new ResolutionSlider(mOutput, this); connect(mResolution, &ResolutionSlider::resolutionChanged, this, &OutputConfig::slotResolutionChanged); formLayout->addRow(i18n("Resolution:"), mResolution); mRotation = new QComboBox(this); QIcon previewIcon = QIcon::fromTheme(QStringLiteral("view-preview")); QPixmap previewPixmap = previewIcon.pixmap(mRotation->iconSize()); QIcon previewRotatedCounterClockwise = QIcon(previewPixmap.transformed(QMatrix(0.0, -1.0, 1.0, 0.0, 0.0, 0.0))); QIcon previewRotatedClockwise = QIcon(previewPixmap.transformed(QMatrix(0.0, 1.0, -1.0, 0.0, 0.0, 0.0))); QIcon previewRotatedUpSideDown = QIcon(previewPixmap.transformed(QMatrix(-1.0, 0.0, 0.0, -1.0, 0.0, 0.0))); mRotation->addItem(previewIcon, i18n("No Rotation"), KScreen::Output::None); mRotation->addItem(previewRotatedClockwise, i18n("90° Clockwise"), KScreen::Output::Right); mRotation->addItem(previewRotatedUpSideDown, i18n("Upside Down"), KScreen::Output::Inverted); mRotation->addItem(previewRotatedCounterClockwise, i18n("90° Counterclockwise"), KScreen::Output::Left); connect(mRotation, static_cast(&QComboBox::activated), this, &OutputConfig::slotRotationChanged); mRotation->setCurrentIndex(mRotation->findData(mOutput->rotation())); formLayout->addRow(i18n("Orientation:"), mRotation); if (mShowScaleOption) { mScale = new QComboBox(this); mScale->addItem(i18nc("Scale multiplier, show everything at 1 times normal scale", "1x"), 1); mScale->addItem(i18nc("Scale multiplier, show everything at 2 times normal scale", "2x"), 2); connect(mScale, static_cast(&QComboBox::activated), this, &OutputConfig::slotScaleChanged); mScale->setCurrentIndex(mScale->findData(mOutput->scale())); formLayout->addRow(i18n("Scale:"), mScale); formLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum)); } mRefreshRate = new QComboBox(this); mRefreshRate->addItem(i18n("Auto"), -1); formLayout->addRow(i18n("Refresh rate:"), mRefreshRate); 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(); } KScreen::OutputPtr OutputConfig::output() const { return mOutput; } void OutputConfig::slotResolutionChanged(const QSize &size) { // Ignore disconnected outputs if (!size.isValid()) { return; } KScreen::ModePtr selectedMode; QList modes; Q_FOREACH (const KScreen::ModePtr &mode, mOutput->modes()) { if (mode->size() == size) { modes << mode; if (!selectedMode || selectedMode->refreshRate() < mode->refreshRate()) { selectedMode = mode; } } } Q_ASSERT(selectedMode); mOutput->setCurrentModeId(selectedMode->id()); // Don't remove the first "Auto" item - prevents ugly flicker of the combobox // when changing resolution for (int i = 1; i < mRefreshRate->count(); ++i) { mRefreshRate->removeItem(i); } for (int i = 0, total = modes.count(); i < total; ++i) { const KScreen::ModePtr mode = modes.at(i); mRefreshRate->addItem(i18n("%1 Hz", QLocale().toString(mode->refreshRate(), 'f', 2)), mode->id()); // If selected refresh rate is other then what we consider the "Auto" value // - that is it's not the highest resolution - then select it, otherwise // we stick with "Auto" if (mode == selectedMode && i > 1) { // i + 1 since 0 is auto mRefreshRate->setCurrentIndex(i + 1); } } - + mChanged = true; Q_EMIT changed(); } void OutputConfig::slotRotationChanged(int index) { KScreen::Output::Rotation rotation = static_cast(mRotation->itemData(index).toInt()); mOutput->setRotation(rotation); + mChanged = true; Q_EMIT changed(); } void OutputConfig::slotRefreshRateChanged(int index) { QString modeId; if (index == 0) { // Item 0 is "Auto" - "Auto" is equal to highest refresh rate (at least // that's how I understand it, and since the combobox is sorted in descending // order, we just pick the second item from top modeId = mRefreshRate->itemData(1).toString(); } else { modeId = mRefreshRate->itemData(index).toString(); } 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(); } void OutputConfig::setShowScaleOption(bool showScaleOption) { mShowScaleOption = showScaleOption; if (mOutput) { initUi(); } } bool OutputConfig::showScaleOption() const { 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/outputconfig.h b/kcm/src/outputconfig.h index 9017a71..40fd523 100644 --- a/kcm/src/outputconfig.h +++ b/kcm/src/outputconfig.h @@ -1,75 +1,91 @@ /* * Copyright 2013 Daniel Vrátil * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 OUTPUTCONFIG_H #define OUTPUTCONFIG_H -#include +#include "../../common/control.h" + #include #include +#include #include class QCheckBox; +class QGroupBox; class ResolutionSlider; class QLabel; class OutputConfig : public QWidget { Q_OBJECT public: explicit OutputConfig(QWidget *parent); 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); void slotRefreshRateChanged(int index); void slotScaleChanged(int index); Q_SIGNALS: void changed(); protected: virtual void initUi(); protected: QLabel *mTitle = nullptr; KScreen::OutputPtr mOutput; QCheckBox *mEnabled = nullptr; ResolutionSlider *mResolution = nullptr; QComboBox *mRotation = nullptr; 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/unifiedoutputconfig.cpp b/kcm/src/unifiedoutputconfig.cpp index 9518272..db02b5d 100644 --- a/kcm/src/unifiedoutputconfig.cpp +++ b/kcm/src/unifiedoutputconfig.cpp @@ -1,197 +1,199 @@ /* * Copyright 2013 Daniel Vrátil * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 "unifiedoutputconfig.h" #include "resolutionslider.h" #include "utils.h" #include "kcm_screen_debug.h" #include #include #include #include #include #include #include #include #include #include #include bool operator<(const QSize &s1, const QSize &s2) { return s1.width() * s1.height() < s2.width() * s2.height(); } template<> bool qMapLessThanKey(const QSize &s1, const QSize &s2) { return s1 < s2; } UnifiedOutputConfig::UnifiedOutputConfig(const KScreen::ConfigPtr &config, QWidget *parent) : OutputConfig(parent) , mConfig(config) { } UnifiedOutputConfig::~UnifiedOutputConfig() { } -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(); mClones.reserve(mOutput->clones().count()); Q_FOREACH (int id, mOutput->clones()) { mClones << mConfig->output(id); } mClones << mOutput; OutputConfig::setOutput(output); } void UnifiedOutputConfig::initUi() { QVBoxLayout *vbox = new QVBoxLayout(this); mTitle = new QLabel(this); mTitle->setAlignment(Qt::AlignHCenter); vbox->addWidget(mTitle); setTitle(i18n("Unified Outputs")); QGridLayout *formLayout = new QGridLayout(); vbox->addLayout(formLayout); vbox->addStretch(2); KScreen::OutputPtr fakeOutput = createFakeOutput(); mResolution = new ResolutionSlider(fakeOutput, this); connect(mResolution, &ResolutionSlider::resolutionChanged, this, &UnifiedOutputConfig::slotResolutionChanged); formLayout->addWidget(new QLabel(i18n("Resolution:"), this), 1, 0); formLayout->addWidget(mResolution, 1, 1); slotResolutionChanged(mResolution->currentResolution()); mRotation = new QComboBox(this); connect(mRotation, static_cast(&QComboBox::currentIndexChanged), this, &UnifiedOutputConfig::slotRotationChanged); mRotation->addItem(QIcon::fromTheme(QStringLiteral("arrow-up")), i18n("Normal"), KScreen::Output::None); mRotation->addItem(QIcon::fromTheme(QStringLiteral("arrow-right")), i18n("90° Clockwise"), KScreen::Output::Right); mRotation->addItem(QIcon::fromTheme(QStringLiteral("arrow-down")), i18n("Upside Down"), KScreen::Output::Inverted); mRotation->addItem(QIcon::fromTheme(QStringLiteral("arrow-left")), i18n("90° Counterclockwise"), KScreen::Output::Left); formLayout->addWidget(new QLabel(i18n("Orientation:"), this), 2, 0); formLayout->addWidget(mRotation, 2, 1); formLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum), 0, 2, 3, 1); } KScreen::OutputPtr UnifiedOutputConfig::createFakeOutput() { // Find set of common resolutions QMap commonSizes; Q_FOREACH (const KScreen::OutputPtr &clone, mClones) { QList processedSizes; Q_FOREACH (const KScreen::ModePtr &mode, clone->modes()) { // Make sure we don't count some modes multiple times because of different // refresh rates if (processedSizes.contains(mode->size())) { continue; } processedSizes << mode->size(); if (commonSizes.contains(mode->size())) { commonSizes[mode->size()]++; } else { commonSizes.insert(mode->size(), 1); } } } KScreen::OutputPtr fakeOutput(new KScreen::Output); // This will give us list of resolution that are shared by all outputs QList commonResults = commonSizes.keys(mClones.count()); // If there are no common resolution, fallback to smallest preferred mode if (commonResults.isEmpty()) { QSize smallestMode; Q_FOREACH (const KScreen::OutputPtr &clone, mClones) { qCDebug(KSCREEN_KCM) << smallestMode << clone->preferredMode()->size(); if (!smallestMode.isValid() || clone->preferredMode()->size() < smallestMode) { smallestMode = clone->preferredMode()->size(); } } commonResults << smallestMode; } std::sort(commonResults.begin(), commonResults.end()); KScreen::ModeList modes; Q_FOREACH (const QSize &size, commonResults) { KScreen::ModePtr mode(new KScreen::Mode); mode->setSize(size); mode->setId(Utils::sizeToString(size)); mode->setName(mode->id()); modes.insert(mode->id(), mode); } fakeOutput->setModes(modes); fakeOutput->setCurrentModeId(Utils::sizeToString(commonResults.last())); return fakeOutput; } void UnifiedOutputConfig::slotResolutionChanged(const QSize &size) { // Ignore disconnected outputs if (!size.isValid()) { return; } Q_FOREACH (const KScreen::OutputPtr &clone, mClones) { const QString &id = findBestMode(clone, size); if (id.isEmpty()) { // FIXME: Error? return; } clone->setCurrentModeId(id); } Q_EMIT changed(); } QString UnifiedOutputConfig::findBestMode(const KScreen::OutputPtr &output, const QSize &size) { float refreshRate = 0; QString id; Q_FOREACH (const KScreen::ModePtr &mode, output->modes()) { if (mode->size() == size && mode->refreshRate() > refreshRate) { refreshRate = mode->refreshRate(); id = mode->id(); } } return id; } diff --git a/kcm/src/unifiedoutputconfig.h b/kcm/src/unifiedoutputconfig.h index 021c8d8..fa5014d 100644 --- a/kcm/src/unifiedoutputconfig.h +++ b/kcm/src/unifiedoutputconfig.h @@ -1,55 +1,55 @@ /* * Copyright 2013 Daniel Vrátil * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 UNIFIEDOUTPUTCONFIG_H #define UNIFIEDOUTPUTCONFIG_H #include "outputconfig.h" namespace KScreen { class Output; class Config; } class UnifiedOutputConfig : public OutputConfig { Q_OBJECT public: 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); private: void initUi() override; KScreen::OutputPtr createFakeOutput(); QString findBestMode(const KScreen::OutputPtr &output, const QSize &size); private: KScreen::ConfigPtr mConfig; QList mClones; }; #endif // UNIFIEDOUTPUTCONFIG_H diff --git a/kcm/src/widget.cpp b/kcm/src/widget.cpp index c156e69..dd733f9 100644 --- a/kcm/src/widget.cpp +++ b/kcm/src/widget.cpp @@ -1,485 +1,487 @@ /* * Copyright (C) 2013 Daniel Vr??til * * 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ #include "widget.h" #include "controlpanel.h" #include #include #include #include #include #include "declarative/qmloutput.h" #include "declarative/qmlscreen.h" #include "utils.h" #include "scalingconfig.h" +#include "../../common/control.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_kscreen_widget.h" #define QML_PATH "kcm_kscreen/qml/" Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::KScreenWidget()) { qRegisterMetaType(); ui->setupUi(this); ui->quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); connect(ui->primaryCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &Widget::primaryOutputSelected); mControlPanel = new ControlPanel(this); connect(mControlPanel, &ControlPanel::changed, this, &Widget::changed); ui->controlPanelLayout->addWidget(mControlPanel); connect(ui->unifyButton, &QPushButton::released, [this]{ slotUnifyOutputs(); }); connect(ui->scaleAllOutputsButton, &QPushButton::released, [this] { QPointer dialog = new ScalingConfig(mConfig->outputs(), this); dialog->exec(); delete dialog; }); + connect(this, &Widget::saveControls, mControlPanel, &ControlPanel::save); mOutputTimer = new QTimer(this); connect(mOutputTimer, &QTimer::timeout, this, &Widget::clearOutputIdentifiers); loadQml(); } Widget::~Widget() { clearOutputIdentifiers(); delete ui; } bool Widget::eventFilter(QObject* object, QEvent* event) { if (event->type() == QEvent::Resize) { if (mOutputIdentifiers.contains(qobject_cast(object))) { QResizeEvent *e = static_cast(event); const QRect screenSize = object->property("screenSize").toRect(); QRect geometry(QPoint(0, 0), e->size()); geometry.moveCenter(screenSize.center()); static_cast(object)->setGeometry(geometry); // Pass the event further } } return QObject::eventFilter(object, event); } void Widget::setConfig(const KScreen::ConfigPtr &config) { if (mConfig) { KScreen::ConfigMonitor::instance()->removeConfig(mConfig); for (const KScreen::OutputPtr &output : mConfig->outputs()) { output->disconnect(this); } mConfig->disconnect(this); } mConfig = config; KScreen::ConfigMonitor::instance()->addConfig(mConfig); resetPrimaryCombo(); connect(mConfig.data(), &KScreen::Config::outputAdded, this, &Widget::outputAdded); connect(mConfig.data(), &KScreen::Config::outputRemoved, this, &Widget::outputRemoved); connect(mConfig.data(), &KScreen::Config::primaryOutputChanged, this, &Widget::primaryOutputChanged); mScreen->setConfig(mConfig); mControlPanel->setConfig(mConfig); ui->unifyButton->setEnabled(mConfig->outputs().count() > 1); ui->scaleAllOutputsButton->setVisible(!mConfig->supportedFeatures().testFlag(KScreen::Config::Feature::PerOutputScaling)); for (const KScreen::OutputPtr &output : mConfig->outputs()) { outputAdded(output); } // Select the primary (or only) output by default QMLOutput *qmlOutput = mScreen->primaryOutput(); if (qmlOutput) { mScreen->setActiveOutput(qmlOutput); } else { if (!mScreen->outputs().isEmpty()) { mScreen->setActiveOutput(mScreen->outputs().at(0)); } } slotOutputEnabledChanged(); } KScreen::ConfigPtr Widget::currentConfig() const { return mConfig; } void Widget::loadQml() { qmlRegisterType("org.kde.kscreen", 1, 0, "QMLOutput"); qmlRegisterType("org.kde.kscreen", 1, 0, "QMLScreen"); qmlRegisterType("org.kde.kscreen", 1, 0, "KScreenOutput"); qmlRegisterType("org.kde.kscreen", 1, 0, "KScreenEdid"); qmlRegisterType("org.kde.kscreen", 1, 0, "KScreenMode"); //const QString file = QDir::currentPath() + "/main.qml"; const QString file = QStandardPaths::locate(QStandardPaths::QStandardPaths::GenericDataLocation, QStringLiteral("kcm_kscreen/qml/main.qml")); ui->quickWidget->setSource(QUrl::fromLocalFile(file)); QQuickItem* rootObject = ui->quickWidget->rootObject(); mScreen = rootObject->findChild(QStringLiteral("outputView")); if (!mScreen) { return; } connect(mScreen, &QMLScreen::focusedOutputChanged, this, &Widget::slotFocusedOutputChanged); connect(rootObject->findChild(QStringLiteral("identifyButton")), SIGNAL(clicked()), this, SLOT(slotIdentifyButtonClicked())); } void Widget::resetPrimaryCombo() { bool isPrimaryDisplaySupported = mConfig->supportedFeatures().testFlag(KScreen::Config::Feature::PrimaryDisplay); ui->primaryLabel->setVisible(isPrimaryDisplaySupported); ui->primaryCombo->setVisible(isPrimaryDisplaySupported); // Don't emit currentIndexChanged when resetting bool blocked = ui->primaryCombo->blockSignals(true); ui->primaryCombo->clear(); ui->primaryCombo->addItem(i18n("No Primary Output")); ui->primaryCombo->blockSignals(blocked); if (!mConfig) { return; } for (auto &output: mConfig->outputs()) { addOutputToPrimaryCombo(output); } } void Widget::addOutputToPrimaryCombo(const KScreen::OutputPtr &output) { if (!output->isConnected() || !output->isEnabled()) { return; } ui->primaryCombo->addItem(Utils::outputName(output), output->id()); if (output->isPrimary()) { Q_ASSERT(mConfig); int lastIndex = ui->primaryCombo->count() - 1; ui->primaryCombo->setCurrentIndex(lastIndex); } } void Widget::slotFocusedOutputChanged(QMLOutput *output) { mControlPanel->activateOutput(output->outputPtr()); } void Widget::slotOutputEnabledChanged() { resetPrimaryCombo(); int enabledOutputsCount = 0; Q_FOREACH (const KScreen::OutputPtr &output, mConfig->outputs()) { if (output->isEnabled()) { ++enabledOutputsCount; } if (enabledOutputsCount > 1) { break; } } ui->unifyButton->setEnabled(enabledOutputsCount > 1); } void Widget::slotOutputConnectedChanged() { resetPrimaryCombo(); } void Widget::slotUnifyOutputs() { QMLOutput *base = mScreen->primaryOutput(); QList clones; if (!base) { for (QMLOutput *output: mScreen->outputs()) { if (output->output()->isConnected() && output->output()->isEnabled()) { base = output; break; } } if (!base) { // WTF? return; } } if (base->isCloneMode()) { setConfig(mPrevConfig); mPrevConfig.clear(); ui->primaryCombo->setEnabled(true); ui->unifyButton->setText(i18n("Unify Outputs")); } else { // Clone the current config, so that we can restore it in case user // breaks the cloning mPrevConfig = mConfig->clone(); for (QMLOutput *output: mScreen->outputs()) { if (!output->output()->isConnected()) { continue; } if (!output->output()->isEnabled()) { output->setVisible(false); continue; } if (!base) { base = output; } output->setOutputX(0); output->setOutputY(0); output->output()->setPos(QPoint(0, 0)); output->output()->setClones(QList()); if (base != output) { clones << output->output()->id(); output->setCloneOf(base); output->setVisible(false); } } base->output()->setClones(clones); base->setIsCloneMode(true); mScreen->updateOutputsPlacement(); ui->primaryCombo->setEnabled(false); mControlPanel->setUnifiedOutput(base->outputPtr()); ui->unifyButton->setText(i18n("Break Unified Outputs")); } Q_EMIT changed(); } // FIXME: Copy-pasted from KDED's Serializer::findOutput() KScreen::OutputPtr Widget::findOutput(const KScreen::ConfigPtr &config, const QVariantMap &info) { KScreen::OutputList outputs = config->outputs(); Q_FOREACH(const KScreen::OutputPtr &output, outputs) { if (!output->isConnected()) { continue; } const QString outputId = (output->edid() && output->edid()->isValid()) ? output->edid()->hash() : output->name(); if (outputId != info[QStringLiteral("id")].toString()) { continue; } 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()); output->setRotation(static_cast(info[QStringLiteral("rotation")].toInt())); QVariantMap modeInfo = info[QStringLiteral("mode")].toMap(); QVariantMap modeSize = modeInfo[QStringLiteral("size")].toMap(); QSize size(modeSize[QStringLiteral("width")].toInt(), modeSize[QStringLiteral("height")].toInt()); const KScreen::ModeList modes = output->modes(); Q_FOREACH(const KScreen::ModePtr &mode, modes) { if (mode->size() != size) { continue; } if (QString::number(mode->refreshRate()) != modeInfo[QStringLiteral("refresh")].toString()) { continue; } output->setCurrentModeId(mode->id()); break; } return output; } return KScreen::OutputPtr(); } void Widget::clearOutputIdentifiers() { mOutputTimer->stop(); qDeleteAll(mOutputIdentifiers); mOutputIdentifiers.clear(); } void Widget::outputAdded(const KScreen::OutputPtr &output) { connect(output.data(), &KScreen::Output::isConnectedChanged, this, &Widget::slotOutputConnectedChanged); connect(output.data(), &KScreen::Output::isEnabledChanged, this, &Widget::slotOutputEnabledChanged); connect(output.data(), &KScreen::Output::posChanged, this, &Widget::changed); addOutputToPrimaryCombo(output); } void Widget::outputRemoved(int outputId) { KScreen::OutputPtr output = mConfig->output(outputId); if (!output.isNull()) { output->disconnect(this); } const int index = ui->primaryCombo->findData(outputId); if (index == -1) { return; } if (index == ui->primaryCombo->currentIndex()) { // We'll get the actual primary update signal eventually // Don't emit currentIndexChanged const bool blocked = ui->primaryCombo->blockSignals(true); ui->primaryCombo->setCurrentIndex(0); ui->primaryCombo->blockSignals(blocked); } ui->primaryCombo->removeItem(index); } void Widget::primaryOutputSelected(int index) { if (!mConfig) { return; } const KScreen::OutputPtr newPrimary = index == 0 ? KScreen::OutputPtr() : mConfig->output(ui->primaryCombo->itemData(index).toInt()); if (newPrimary == mConfig->primaryOutput()) { return; } mConfig->setPrimaryOutput(newPrimary); Q_EMIT changed(); } void Widget::primaryOutputChanged(const KScreen::OutputPtr &output) { Q_ASSERT(mConfig); int index = output.isNull() ? 0 : ui->primaryCombo->findData(output->id()); if (index == -1 || index == ui->primaryCombo->currentIndex()) { return; } ui->primaryCombo->setCurrentIndex(index); } void Widget::slotIdentifyButtonClicked(bool checked) { Q_UNUSED(checked); connect(new KScreen::GetConfigOperation(), &KScreen::GetConfigOperation::finished, this, &Widget::slotIdentifyOutputs); } void Widget::slotIdentifyOutputs(KScreen::ConfigOperation *op) { if (op->hasError()) { return; } const KScreen::ConfigPtr config = qobject_cast(op)->config(); const QString qmlPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral(QML_PATH "OutputIdentifier.qml")); mOutputTimer->stop(); clearOutputIdentifiers(); /* Obtain the current active configuration from KScreen */ Q_FOREACH (const KScreen::OutputPtr &output, config->outputs()) { if (!output->isConnected() || !output->currentMode()) { continue; } const KScreen::ModePtr mode = output->currentMode(); QQuickView *view = new QQuickView(); view->setFlags(Qt::X11BypassWindowManagerHint | Qt::FramelessWindowHint); view->setResizeMode(QQuickView::SizeViewToRootObject); view->setSource(QUrl::fromLocalFile(qmlPath)); view->installEventFilter(this); QQuickItem *rootObj = view->rootObject(); if (!rootObj) { qWarning() << "Failed to obtain root item"; continue; } QSize deviceSize, logicalSize; if (output->isHorizontal()) { deviceSize = mode->size(); } else { deviceSize = QSize(mode->size().height(), mode->size().width()); } if (config->supportedFeatures() & KScreen::Config::Feature::PerOutputScaling) { // no scale adjustment needed on Wayland logicalSize = deviceSize; } else { logicalSize = deviceSize / devicePixelRatioF(); } rootObj->setProperty("outputName", Utils::outputName(output)); rootObj->setProperty("modeName", Utils::sizeToString(deviceSize)); view->setProperty("screenSize", QRect(output->pos(), logicalSize)); mOutputIdentifiers << view; } for (QQuickView *view: mOutputIdentifiers) { view->show(); } mOutputTimer->start(2500); } diff --git a/kcm/src/widget.h b/kcm/src/widget.h index 745ad53..def1b27 100644 --- a/kcm/src/widget.h +++ b/kcm/src/widget.h @@ -1,98 +1,99 @@ /* * Copyright (C) 2013 Daniel Vrátil * * 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ #ifndef WIDGET_H #define WIDGET_H #include #include #include class QMLOutput; class QMLScreen; class ControlPanel; - +class ControlConfig; class QQuickView; namespace KScreen { class ConfigOperation; } namespace Ui { class KScreenWidget; } class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget *parent = nullptr); ~Widget() override; void setConfig(const KScreen::ConfigPtr &config); KScreen::ConfigPtr currentConfig() const; protected: bool eventFilter(QObject *object, QEvent *event) override; Q_SIGNALS: void changed(); + void saveControls(); private Q_SLOTS: void slotFocusedOutputChanged(QMLOutput *output); void slotOutputEnabledChanged(); void slotOutputConnectedChanged(); void slotUnifyOutputs(); void slotIdentifyButtonClicked(bool checked = true); void slotIdentifyOutputs(KScreen::ConfigOperation *op); void clearOutputIdentifiers(); void outputAdded(const KScreen::OutputPtr &output); void outputRemoved(int outputId); void primaryOutputSelected(int index); void primaryOutputChanged(const KScreen::OutputPtr &output); private: void loadQml(); void resetPrimaryCombo(); void addOutputToPrimaryCombo(const KScreen::OutputPtr &output); KScreen::OutputPtr findOutput(const KScreen::ConfigPtr &config, const QVariantMap &info); private: Ui::KScreenWidget *ui; QMLScreen *mScreen = nullptr; KScreen::ConfigPtr mConfig = nullptr; KScreen::ConfigPtr mPrevConfig = nullptr; ControlPanel *mControlPanel = nullptr; QList mOutputIdentifiers; QTimer *mOutputTimer = nullptr; }; #endif // WIDGET_H