diff --git a/kcm/package/contents/ui/OutputPanel.qml b/kcm/package/contents/ui/OutputPanel.qml index 7ee3bb2..0c4824e 100644 --- a/kcm/package/contents/ui/OutputPanel.qml +++ b/kcm/package/contents/ui/OutputPanel.qml @@ -1,126 +1,145 @@ /******************************************************************** 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 . *********************************************************************/ import QtQuick 2.9 import QtQuick.Layouts 1.1 import QtQuick.Controls 2.3 as Controls import org.kde.kirigami 2.4 as Kirigami import org.kde.kcm 1.2 as KCM ColumnLayout { id: outputPanel property var element: model Kirigami.Heading { Layout.fillWidth: true horizontalAlignment: Text.AlignHCenter level: 2 text: i18n("Settings for %1", element.display) visible: kcm.outputModel.rowCount() > 1 } Kirigami.FormLayout { twinFormLayouts: globalSettingsLayout Controls.CheckBox { text: i18n("Enabled") checked: element.enabled onClicked: element.enabled = checked visible: kcm.outputModel.rowCount() > 1 } Controls.CheckBox { text: i18n("Primary") checked: element.primary onClicked: element.primary = checked visible: kcm.primaryOutputSupported && kcm.outputModel.rowCount() > 1 } Controls.ComboBox { Kirigami.FormData.label: i18n("Resolution:") model: element.resolutions currentIndex: element.resolutionIndex !== undefined ? element.resolutionIndex : -1 onActivated: element.resolutionIndex = currentIndex } RowLayout { Layout.fillWidth: true visible: kcm.perOutputScaling Kirigami.FormData.label: i18n("Scale:") Controls.Slider { id: scaleSlider Layout.fillWidth: true from: 0.5 to: 3 stepSize: 0.1 live: true value: element.scale onMoved: element.scale = value } - Controls.Label { - Layout.alignment: Qt.AlignHCenter - text: i18nc("Scale factor (e.g. 1.0x, 1.5x, 2.0x)","%1x", scaleSlider.value.toLocaleString(Qt.locale(), "f", 1)) + Controls.SpinBox { + id: spinbox + // Because QQC2 SpinBox doesn't natively support decimal step + // sizes: https://bugreports.qt.io/browse/QTBUG-67349 + property real factor: 20.0 + property real realValue: value / factor + + from : 0.5 * factor + to : 3.0 * factor + stepSize: 0.05 * factor + value: element.scale * factor + validator: DoubleValidator { + bottom: Math.min(spinbox.from, spinbox.to) * spinbox.factor + top: Math.max(spinbox.from, spinbox.to) * spinbox.factor + } + textFromValue: function(value, locale) { + return parseFloat(value * 1.0 / factor).toFixed(2); + } + valueFromText: function(text, locale) { + return Number.fromLocaleString(locale, text) * factor + } + onValueModified: element.scale = realValue } } Controls.ButtonGroup { buttons: orientation.children } RowLayout { id: orientation Kirigami.FormData.label: i18n("Orientation:") RotationButton { value: 0 } RotationButton { value: 90 } RotationButton { value: 180 } RotationButton { value: 270 } } Controls.ComboBox { Kirigami.FormData.label: i18n("Refresh rate:") model: element.refreshRates currentIndex: element.refreshRateIndex ? element.refreshRateIndex : 0 onActivated: element.refreshRateIndex = currentIndex } Controls.ComboBox { Kirigami.FormData.label: i18n("Replica of:") model: element.replicationSourceModel visible: kcm.outputReplicationSupported && kcm.outputModel && kcm.outputModel.rowCount() > 1 onModelChanged: enabled = (count > 1); onCountChanged: enabled = (count > 1); currentIndex: element.replicationSourceIndex onActivated: element.replicationSourceIndex = currentIndex } } } diff --git a/kcm/package/contents/ui/Panel.qml b/kcm/package/contents/ui/Panel.qml index 3b66464..2ecaad8 100644 --- a/kcm/package/contents/ui/Panel.qml +++ b/kcm/package/contents/ui/Panel.qml @@ -1,109 +1,145 @@ /******************************************************************** 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 . *********************************************************************/ import QtQuick 2.9 import QtQuick.Layouts 1.1 import QtQuick.Controls 2.3 as Controls import org.kde.plasma.extras 2.0 as PlasmaExtras import org.kde.kirigami 2.4 as Kirigami import org.kde.private.kcm.kscreen 1.0 as KScreen ColumnLayout { Controls.SwipeView { id: panelView currentIndex: root.selectedOutput onCurrentIndexChanged: root.selectedOutput = Qt.binding(function() { return currentIndex; }); Layout.fillWidth: true Repeater { model: kcm.outputModel OutputPanel {} } } Controls.PageIndicator { id: indicator Layout.alignment: Qt.AlignHCenter visible: count > 1 count: panelView.count currentIndex: panelView.currentIndex interactive: true onCurrentIndexChanged: root.selectedOutput = currentIndex } Kirigami.FormLayout { id: globalSettingsLayout Layout.fillWidth: true Kirigami.Separator { Layout.fillWidth: true Kirigami.FormData.isSection: true } RowLayout { Layout.fillWidth: true Kirigami.FormData.label: i18n("Global scale:") visible: !kcm.perOutputScaling Controls.Slider { id: globalScaleSlider Layout.fillWidth: true from: 1 to: 3 - stepSize: 0.1 + stepSize: 0.25 live: true value: kcm.globalScale - onMoved: kcm.globalScale = value + onMoved: kcm.globalScale = value; } - Controls.Label { - text: i18nc("Scale factor (e.g. 1.0x, 1.5x, 2.0x)","%1x", globalScaleSlider.value.toLocaleString(Qt.locale(), "f", 1)) + Controls.SpinBox { + id: spinbox + // Because QQC2 SpinBox doesn't natively support decimal step + // sizes: https://bugreports.qt.io/browse/QTBUG-67349 + property real factor: 20.0 + property real realValue: value / factor + + from : 1.0 * factor + to : 3.0 * factor + stepSize: 0.05 * factor + value: kcm.globalScale * factor + validator: DoubleValidator { + bottom: Math.min(spinbox.from, spinbox.to)*spinbox.factor + top: Math.max(spinbox.from, spinbox.to)*spinbox.factor + } + textFromValue: function(value, locale) { + return parseFloat(value * 1.0 / factor).toFixed(2); + } + valueFromText: function(text, locale) { + return Number.fromLocaleString(locale, text) * factor + } + onValueModified: { + kcm.globalScale = realValue; + if (kcm.globalScale % 0.25) { + weirdScaleFactorMsg.visible = true; + } else { + weirdScaleFactorMsg.visible = false; + } + } } } + Kirigami.InlineMessage { + id: weirdScaleFactorMsg + Layout.fillWidth: true + type: Kirigami.MessageType.Warning + text: i18n("Scale factors that are not a multiple of 0.25 may cause visual glitches in applications. Consider setting the scale factor to a multiple of 0.25 and adjusting the font size instead.") + visible: false + showCloseButton: true + } + Controls.ButtonGroup { buttons: retentionSelector.children } ColumnLayout { id: retentionSelector Kirigami.FormData.label: i18n("Save displays' properties:") Kirigami.FormData.buddyFor: globalRetentionRadio spacing: Kirigami.Units.smallSpacing Controls.RadioButton { id: globalRetentionRadio text: i18n("For any display arrangement") checked: !individualRetentionRadio.checked onClicked: kcm.outputRetention = KScreen.Control.Global } Controls.RadioButton { id: individualRetentionRadio text: i18n("For only this specific display arrangement") checked: kcm.outputRetention === KScreen.Control.Individual onClicked: kcm.outputRetention = KScreen.Control.Individual } } } } diff --git a/kcm/package/contents/ui/main.qml b/kcm/package/contents/ui/main.qml index 0bdad02..157d68a 100644 --- a/kcm/package/contents/ui/main.qml +++ b/kcm/package/contents/ui/main.qml @@ -1,129 +1,129 @@ /******************************************************************** 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 . *********************************************************************/ import QtQuick 2.9 import QtQuick.Layouts 1.1 import QtQuick.Controls 2.3 as Controls import org.kde.kirigami 2.4 as Kirigami import org.kde.kcm 1.2 as KCM KCM.SimpleKCM { id: root - implicitWidth: units.gridUnit * 30 + implicitWidth: units.gridUnit * 32 implicitHeight: units.gridUnit * 38 property int selectedOutput: 0 ColumnLayout { Kirigami.InlineMessage { // Note1: There is an implicit height binding loop error on // first invokation. Seems to be an issue in Kirigami. // Note2: This should maybe go in header component of the KCM, // but there seems to be another issue in Kirigami then // being always hidden. Compare Night Color KCM with // the same issue. id: dangerousSaveMsg Layout.fillWidth: true type: Kirigami.MessageType.Warning text: i18n("Are you sure you want to disable all outputs? This might render the device unusable.") showCloseButton: true actions: [ Kirigami.Action { iconName: "dialog-ok" text: i18n("Disable All Outputs") onTriggered: { dangerousSaveMsg.visible = false; kcm.forceSave(); } } ] } Kirigami.InlineMessage { id: errBackendMsg Layout.fillWidth: true type: Kirigami.MessageType.Error text: i18n("No KScreen backend found. Please check your KScreen installation.") visible: false showCloseButton: false } Kirigami.InlineMessage { id: errSaveMsg Layout.fillWidth: true type: Kirigami.MessageType.Error text: i18n("Outputs could not be saved due to error.") visible: false showCloseButton: true } Kirigami.InlineMessage { id: scaleMsg Layout.fillWidth: true type: Kirigami.MessageType.Positive text: i18n("New global scale applied. Change will come into effect after restart.") visible: false showCloseButton: true } Kirigami.InlineMessage { id: connectMsg Layout.fillWidth: true type: Kirigami.MessageType.Information visible: false showCloseButton: true } Connections { target: kcm onDangerousSave: dangerousSaveMsg.visible = true; onErrorOnSave: errSaveMsg.visible = true; onGlobalScaleWritten: scaleMsg.visible = true; onOutputConnect: { if (connected) { connectMsg.text = i18n("A new output has been added. Settings have been reloaded."); } else { connectMsg.text = i18n("An output has been removed. Settings have been reloaded."); } connectMsg.visible = true; } onBackendError: errBackendMsg.visible = true; onChanged: { dangerousSaveMsg.visible = false; errSaveMsg.visible = false; scaleMsg.visible = false; } } Screen { id: screen Layout.alignment: Qt.AlignHCenter Layout.preferredWidth: Math.max(root.width * 0.8, units.gridUnit * 26) Layout.topMargin: Kirigami.Units.smallSpacing Layout.bottomMargin: Kirigami.Units.largeSpacing * 2 enabled: kcm.outputModel && kcm.backendReady outputs: kcm.outputModel } Panel { enabled: kcm.outputModel && kcm.backendReady Layout.fillWidth: true } } } diff --git a/kded/output.cpp b/kded/output.cpp index 273a4b1..9477ee2 100644 --- a/kded/output.cpp +++ b/kded/output.cpp @@ -1,408 +1,408 @@ /******************************************************************** 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 "output.h" #include "config.h" #include "../common/globals.h" #include "kscreen_daemon_debug.h" #include "generator.h" #include #include #include #include #include #include #include #include QString Output::s_dirName = QStringLiteral("outputs/"); QString Output::dirPath() { return Globals::dirPath() % s_dirName; } QString Output::globalFileName(const QString &hash) { const auto dir = dirPath(); if (!QDir().mkpath(dir)) { return QString(); } return dir % hash; } void Output::readInGlobalPartFromInfo(KScreen::OutputPtr output, const QVariantMap &info) { output->setRotation(static_cast(info.value(QStringLiteral("rotation"), 1).toInt())); bool scaleOk; const qreal scale = info.value(QStringLiteral("scale"), 1.).toDouble(&scaleOk); if (scaleOk) { output->setScale(scale); } const QVariantMap modeInfo = info[QStringLiteral("mode")].toMap(); const QVariantMap modeSize = modeInfo[QStringLiteral("size")].toMap(); const QSize size = QSize(modeSize[QStringLiteral("width")].toInt(), modeSize[QStringLiteral("height")].toInt()); qCDebug(KSCREEN_KDED) << "Finding a mode for" << size << "@" << modeInfo[QStringLiteral("refresh")].toFloat(); KScreen::ModeList modes = output->modes(); KScreen::ModePtr matchingMode; for(const KScreen::ModePtr &mode : modes) { if (mode->size() != size) { continue; } if (!qFuzzyCompare(mode->refreshRate(), modeInfo[QStringLiteral("refresh")].toFloat())) { continue; } qCDebug(KSCREEN_KDED) << "\tFound: " << mode->id() << " " << mode->size() << "@" << mode->refreshRate(); matchingMode = mode; break; } if (!matchingMode) { qCWarning(KSCREEN_KDED) << "\tFailed to find a matching mode - this means that our config is corrupted" "or a different device with the same serial number has been connected (very unlikely)." "Falling back to preferred modes."; matchingMode = output->preferredMode(); } if (!matchingMode) { qCWarning(KSCREEN_KDED) << "\tFailed to get a preferred mode, falling back to biggest mode."; matchingMode = Generator::biggestMode(modes); } if (!matchingMode) { qCWarning(KSCREEN_KDED) << "\tFailed to get biggest mode. Which means there are no modes. Turning off the screen."; output->setEnabled(false); return; } output->setCurrentModeId(matchingMode->id()); } QVariantMap Output::getGlobalData(KScreen::OutputPtr output) { QFile file(globalFileName(output->hashMd5())); if (!file.open(QIODevice::ReadOnly)) { qCDebug(KSCREEN_KDED) << "Failed to open file" << file.fileName(); return QVariantMap(); } QJsonDocument parser; return parser.fromJson(file.readAll()).toVariant().toMap(); } bool Output::readInGlobal(KScreen::OutputPtr output) { const QVariantMap info = getGlobalData(output); if (info.empty()) { // if info is empty, the global file does not exists, or is in an unreadable state return false; } readInGlobalPartFromInfo(output, info); return true; } void Output::adjustPositions(KScreen::ConfigPtr config, const QVariantList &outputsInfo) { typedef QPair Out; KScreen::OutputList outputs = config->outputs(); QVector sortedOutputs; // for (const KScreen::OutputPtr output : outputs) { sortedOutputs.append(Out(output->id(), output->pos())); } // go from left to right, top to bottom std::sort(sortedOutputs.begin(), sortedOutputs.end(), [](const Out &o1, const Out &o2) { const int x1 = o1.second.x(); const int x2 = o2.second.x(); return x1 < x2 || (x1 == x2 && o1.second.y() < o2.second.y()); }); for (int cnt = 1; cnt < sortedOutputs.length(); cnt++) { auto getOutputInfoProperties = [outputsInfo](KScreen::OutputPtr output, QRect &geo) -> bool { if (!output) { return false; } const auto hash = output->hash(); auto it = std::find_if(outputsInfo.begin(), outputsInfo.end(), [hash](QVariant v) { const QVariantMap info = v.toMap(); return info[QStringLiteral("id")].toString() == hash; } ); if (it == outputsInfo.end()) { return false; } const QVariantMap outputInfo = it->toMap(); const QVariantMap posInfo = outputInfo[QStringLiteral("pos")].toMap(); const QVariant scaleInfo = outputInfo[QStringLiteral("scale")]; const QVariantMap modeInfo = outputInfo[QStringLiteral("mode")].toMap(); const QVariantMap modeSize = modeInfo[QStringLiteral("size")].toMap(); if (posInfo.isEmpty() || modeSize.isEmpty() || !scaleInfo.canConvert()) { return false; } const qreal scale = scaleInfo.toDouble(); if (scale <= 0) { return false; } const QPoint pos = QPoint(posInfo[QStringLiteral("x")].toInt(), posInfo[QStringLiteral("y")].toInt()); const QSize size = QSize(modeSize[QStringLiteral("width")].toInt() / scale, modeSize[QStringLiteral("height")].toInt() / scale); geo = QRect(pos, size); return true; }; // it's guaranteed that we find the following values in the QMap KScreen::OutputPtr prevPtr = outputs.find(sortedOutputs[cnt - 1].first).value(); KScreen::OutputPtr curPtr = outputs.find(sortedOutputs[cnt].first).value(); QRect prevInfoGeo, curInfoGeo; if (!getOutputInfoProperties(prevPtr, prevInfoGeo) || !getOutputInfoProperties(curPtr, curInfoGeo)) { // no info found, nothing can be adjusted for the next output continue; } const QRect prevGeo = prevPtr->geometry(); const QRect curGeo = curPtr->geometry(); // the old difference between previous and current output read from the config file const int xInfoDiff = curInfoGeo.x() - (prevInfoGeo.x() + prevInfoGeo.width()); // the proposed new difference const int prevRight = prevGeo.x() + prevGeo.width(); const int xCorrected = prevRight + prevGeo.width() * xInfoDiff / (double)prevInfoGeo.width(); const int xDiff = curGeo.x() - prevRight; // In the following calculate the y-coorection. This is more involved since we // differentiate between overlapping and non-overlapping pairs and align either // top to top/bottom or bottom to top/bottom const bool yOverlap = prevInfoGeo.y() + prevInfoGeo.height() > curInfoGeo.y() && prevInfoGeo.y() < curInfoGeo.y() + curInfoGeo.height(); // these values determine which horizontal edge of previous output we align with const int topToTopDiffAbs = qAbs(prevInfoGeo.y() - curInfoGeo.y()); const int topToBottomDiffAbs = qAbs(prevInfoGeo.y() - curInfoGeo.y() - curInfoGeo.height()); const int bottomToBottomDiffAbs = qAbs(prevInfoGeo.y() + prevInfoGeo.height() - curInfoGeo.y() - curInfoGeo.height()); const int bottomToTopDiffAbs = qAbs(prevInfoGeo.y() + prevInfoGeo.height() - curInfoGeo.y()); const bool yTopAligned = topToTopDiffAbs < bottomToBottomDiffAbs && topToTopDiffAbs <= bottomToTopDiffAbs || topToBottomDiffAbs < bottomToBottomDiffAbs; int yInfoDiff = curInfoGeo.y() - prevInfoGeo.y(); int yDiff = curGeo.y() - prevGeo.y(); int yCorrected; if (yTopAligned) { // align to previous top if (!yOverlap) { // align previous top with current bottom yInfoDiff += curInfoGeo.height(); yDiff += curGeo.height(); } // When we align with previous top we are interested in the changes to the // current geometry and not in the ones of the previous one. const double yInfoRel = yInfoDiff / (double)curInfoGeo.height(); yCorrected = prevGeo.y() + yInfoRel * curGeo.height(); } else { // align previous bottom... yInfoDiff -= prevInfoGeo.height(); yDiff -= prevGeo.height(); yCorrected = prevGeo.y() + prevGeo.height(); if (yOverlap) { // ... with current bottom yInfoDiff += curInfoGeo.height(); yDiff += curGeo.height(); yCorrected -= curGeo.height(); } // ... else with current top // When we align with previous bottom we are interested in changes to the // previous geometry. const double yInfoRel = yInfoDiff / (double)prevInfoGeo.height(); yCorrected += yInfoRel * prevGeo.height(); } const int x = xDiff == xInfoDiff ? curGeo.x() : xCorrected; const int y = yDiff == yInfoDiff ? curGeo.y() : yCorrected; curPtr->setPos(QPoint(x, y)); } } 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 (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::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; { QStringList allIds; allIds.reserve(outputs.count()); for (const KScreen::OutputPtr &output : outputs) { const auto outputId = output->hash(); if (allIds.contains(outputId) && !duplicateIds.contains(outputId)) { duplicateIds << outputId; } allIds << outputId; } allIds.clear(); } for (KScreen::OutputPtr output : outputs) { if (!output->isConnected()) { output->setEnabled(false); continue; } const auto outputId = output->hash(); bool infoFound = false; for (const auto &variantInfo : outputsInfo) { const QVariantMap info = variantInfo.toMap(); 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) 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, control.getOutputRetention(output)); const QString replicationSourceHash = info[QStringLiteral("replicate")].toString(); if (replicationSourceHash.isEmpty()) { output->setReplicationSource(0); } else { for (const KScreen::OutputPtr out : outputs) { if (out != output && out->hashMd5() == replicationSourceHash) { output->setReplicationSource(out->id()); break; } } } break; } if (!infoFound) { // no info in info for this output, try reading in global output info atleast or set some default values qCWarning(KSCREEN_KDED) << "\tFailed to find a matching output in the current info data - this means that our info is corrupted" "or a different device with the same serial number has been connected (very unlikely)."; if (!readInGlobal(output)) { // set some default values instead readInGlobalPartFromInfo(output, QVariantMap()); } } } // correct positional config regressions on global output data changes adjustPositions(config, outputsInfo); } static QVariantMap metadata(const KScreen::OutputPtr &output) { QVariantMap metadata; metadata[QStringLiteral("name")] = output->name(); if (!output->edid() || !output->edid()->isValid()) { return metadata; } metadata[QStringLiteral("fullname")] = output->edid()->deviceId(); return metadata; } bool Output::writeGlobalPart(const KScreen::OutputPtr &output, QVariantMap &info, const KScreen::OutputPtr &fallback) { info[QStringLiteral("id")] = output->hash(); info[QStringLiteral("metadata")] = metadata(output); info[QStringLiteral("rotation")] = output->rotation(); - // Round scale to one digit. - info[QStringLiteral("scale")] = int(output->scale() * 10 + 0.5) / 10.; + // Round scale to two digits + info[QStringLiteral("scale")] = int(output->scale() * 100 + 0.5) / 100.; QVariantMap modeInfo; float refreshRate = -1.; QSize modeSize; if (output->currentMode() && output->isEnabled()) { refreshRate = output->currentMode()->refreshRate(); modeSize = output->currentMode()->size(); } else if (fallback && fallback->currentMode()) { refreshRate = fallback->currentMode()->refreshRate(); modeSize = fallback->currentMode()->size(); } if (refreshRate < 0 || !modeSize.isValid()) { return false; } modeInfo[QStringLiteral("refresh")] = refreshRate; QVariantMap modeSizeMap; modeSizeMap[QStringLiteral("width")] = modeSize.width(); modeSizeMap[QStringLiteral("height")] = modeSize.height(); modeInfo[QStringLiteral("size")] = modeSizeMap; info[QStringLiteral("mode")] = modeInfo; return true; } void Output::writeGlobal(const KScreen::OutputPtr &output) { // get old values and subsequently override QVariantMap info = getGlobalData(output); if (!writeGlobalPart(output, info, nullptr)) { return; } QFile file(globalFileName(output->hashMd5())); if (!file.open(QIODevice::WriteOnly)) { qCWarning(KSCREEN_KDED) << "Failed to open global output file for writing! " << file.errorString(); return; } file.write(QJsonDocument::fromVariant(info).toJson()); return; }