22#include <QJsonDocument>
24#include <QOrientationReading>
41 QList<Output *> relevantOutputs;
42 std::copy_if(outputs.begin(), outputs.end(), std::back_inserter(relevantOutputs), [](
Output *output) {
43 return !output->isNonDesktop() && !output->isPlaceholder();
45 if (relevantOutputs.isEmpty()) {
48 if (
const auto opt = findSetup(relevantOutputs, isLidClosed)) {
49 const auto &[setup, outputStates] = *opt;
50 auto [config, order] = setupToConfig(setup, outputStates);
51 applyOrientationReading(config, relevantOutputs, orientation, isTabletMode);
52 storeConfig(relevantOutputs, isLidClosed, config, order);
56 auto &[config, order] = *kscreenConfig;
57 applyOrientationReading(config, relevantOutputs, orientation, isTabletMode);
58 storeConfig(relevantOutputs, isLidClosed, config, order);
61 auto [config, order] = generateConfig(relevantOutputs, isLidClosed);
62 applyOrientationReading(config, relevantOutputs, orientation, isTabletMode);
63 storeConfig(relevantOutputs, isLidClosed, config, order);
67void OutputConfigurationStore::applyOrientationReading(
OutputConfiguration &config,
const QList<Output *> &outputs, QOrientationReading *orientation,
bool isTabletMode)
69 const auto output = std::find_if(outputs.begin(), outputs.end(), [&config](
Output *output) {
70 return output->isInternal() && config.changeSet(output)->enabled.value_or(output->isEnabled());
72 if (output == outputs.end()) {
76 const auto changeset = config.
changeSet(*output);
78 changeset->transform = changeset->manualTransform;
81 const auto panelOrientation = (*output)->panelOrientation();
82 switch (orientation->orientation()) {
83 case QOrientationReading::Orientation::TopUp:
84 changeset->transform = panelOrientation;
86 case QOrientationReading::Orientation::TopDown:
89 case QOrientationReading::Orientation::LeftUp:
92 case QOrientationReading::Orientation::RightUp:
95 case QOrientationReading::Orientation::FaceUp:
96 case QOrientationReading::Orientation::FaceDown:
98 case QOrientationReading::Orientation::Undefined:
99 changeset->transform = changeset->manualTransform;
104std::optional<std::pair<OutputConfigurationStore::Setup *, std::unordered_map<Output *, size_t>>> OutputConfigurationStore::findSetup(
const QList<Output *> &outputs,
bool lidClosed)
106 std::unordered_map<Output *, size_t> outputStates;
107 for (Output *output :
outputs) {
108 if (
auto opt = findOutput(output, outputs)) {
109 outputStates[output] = *opt;
114 const auto setup = std::find_if(m_setups.begin(), m_setups.end(), [lidClosed, &outputStates](
const auto &setup) {
115 if (setup.lidClosed != lidClosed || size_t(setup.outputs.size()) != outputStates.size()) {
118 return std::all_of(outputStates.begin(), outputStates.end(), [&setup](
const auto &outputIt) {
119 return std::any_of(setup.outputs.begin(), setup.outputs.end(), [&outputIt](const auto &outputInfo) {
120 return outputInfo.outputIndex == outputIt.second;
124 if (setup == m_setups.end()) {
127 return std::make_pair(&(*setup), outputStates);
131std::optional<size_t> OutputConfigurationStore::findOutput(Output *output,
const QList<Output *> &allOutputs)
const
133 const bool uniqueEdid = !output->edid().identifier().isEmpty() && std::none_of(allOutputs.begin(), allOutputs.end(), [output](Output *otherOutput) {
134 return otherOutput != output && otherOutput->edid().identifier() == output->edid().identifier();
136 const bool uniqueEdidHash = !output->edid().hash().isEmpty() && std::none_of(allOutputs.begin(), allOutputs.end(), [output](Output *otherOutput) {
137 return otherOutput != output && otherOutput->edid().hash() == output->edid().hash();
139 const bool uniqueMst = !output->mstPath().isEmpty() && std::none_of(allOutputs.begin(), allOutputs.end(), [output](Output *otherOutput) {
140 return otherOutput != output && otherOutput->edid().identifier() == output->edid().identifier() && otherOutput->mstPath() == output->mstPath();
142 auto it = std::find_if(m_outputs.begin(), m_outputs.end(), [&](
const auto &outputState) {
143 if (output->edid().isValid()) {
144 if (outputState.edidIdentifier != output->edid().identifier()) {
146 } else if (uniqueEdid) {
150 if (!output->edid().hash().isEmpty()) {
151 if (outputState.edidHash != output->edid().hash()) {
153 }
else if (uniqueEdidHash) {
157 if (outputState.mstPath != output->mstPath()) {
159 }
else if (uniqueMst) {
162 return outputState.connectorName == output->name();
164 if (it == m_outputs.end() && uniqueEdidHash) {
166 it = std::find_if(m_outputs.begin(), m_outputs.end(), [&](
const auto &outputState) {
167 return outputState.edidHash == output->edid().hash();
170 if (it != m_outputs.end()) {
171 return std::distance(m_outputs.begin(), it);
177void OutputConfigurationStore::storeConfig(
const QList<Output *> &allOutputs,
bool isLidClosed,
const OutputConfiguration &config,
const QList<Output *> &outputOrder)
179 QList<Output *> relevantOutputs;
180 std::copy_if(allOutputs.begin(), allOutputs.end(), std::back_inserter(relevantOutputs), [](
Output *output) {
181 return !output->isNonDesktop() && !output->isPlaceholder();
183 if (relevantOutputs.isEmpty()) {
186 const auto opt = findSetup(relevantOutputs, isLidClosed);
187 Setup *setup =
nullptr;
191 m_setups.push_back(Setup{});
192 setup = &m_setups.back();
193 setup->lidClosed = isLidClosed;
195 for (
Output *output : relevantOutputs) {
196 auto outputIndex = findOutput(output, outputOrder);
198 m_outputs.push_back(OutputState{});
199 outputIndex = m_outputs.size() - 1;
201 auto outputIt = std::find_if(setup->outputs.begin(), setup->outputs.end(), [outputIndex](
const auto &output) {
202 return output.outputIndex == outputIndex;
204 if (outputIt == setup->outputs.end()) {
205 setup->outputs.push_back(SetupState{});
206 outputIt = setup->outputs.end() - 1;
209 std::shared_ptr<OutputMode> mode = changeSet->mode.value_or(output->
currentMode()).lock();
213 m_outputs[*outputIndex] = OutputState{
215 .connectorName = output->
name(),
219 .size = mode->size(),
220 .refreshRate = mode->refreshRate(),
222 .scale = changeSet->scale.value_or(output->
scale()),
223 .transform = changeSet->transform.value_or(output->
transform()),
224 .manualTransform = changeSet->manualTransform.value_or(output->
manualTransform()),
225 .overscan = changeSet->overscan.value_or(output->
overscan()),
226 .rgbRange = changeSet->rgbRange.value_or(output->
rgbRange()),
227 .vrrPolicy = changeSet->vrrPolicy.value_or(output->
vrrPolicy()),
228 .highDynamicRange = changeSet->highDynamicRange.value_or(output->
highDynamicRange()),
229 .sdrBrightness = changeSet->sdrBrightness.value_or(output->
sdrBrightness()),
230 .wideColorGamut = changeSet->wideColorGamut.value_or(output->
wideColorGamut()),
232 .iccProfilePath = changeSet->iccProfilePath.value_or(output->
iccProfilePath()),
236 .sdrGamutWideness = changeSet->sdrGamutWideness.value_or(output->
sdrGamutWideness()),
238 *outputIt = SetupState{
239 .outputIndex = *outputIndex,
240 .position = changeSet->pos.value_or(output->
geometry().topLeft()),
241 .enabled = changeSet->enabled.value_or(output->
isEnabled()),
242 .priority = int(outputOrder.indexOf(output)),
246 m_outputs[*outputIndex] = OutputState{
248 .connectorName = output->
name(),
252 .size = mode->size(),
253 .refreshRate = mode->refreshRate(),
255 .scale = output->
scale(),
271 *outputIt = SetupState{
272 .outputIndex = *outputIndex,
273 .position = output->
geometry().topLeft(),
275 .priority = int(outputOrder.indexOf(output)),
282std::pair<OutputConfiguration, QList<Output *>> OutputConfigurationStore::setupToConfig(Setup *setup,
const std::unordered_map<Output *, size_t> &outputMap)
const
285 QList<std::pair<Output *, size_t>> priorities;
286 for (
const auto &[output, outputIndex] : outputMap) {
287 const OutputState &state = m_outputs[outputIndex];
288 const auto &setupState = *std::find_if(setup->outputs.begin(), setup->outputs.end(), [outputIndex = outputIndex](
const auto &state) {
289 return state.outputIndex == outputIndex;
291 const auto modes = output->modes();
292 const auto mode = std::find_if(modes.begin(), modes.end(), [&state](
const auto &mode) {
294 && mode->size() == state.mode->size
295 && mode->refreshRate() == state.mode->refreshRate;
298 .
mode = mode == modes.end() ? std::nullopt : std::optional(*mode),
299 .enabled = setupState.enabled,
300 .pos = setupState.position,
301 .scale = state.scale,
302 .transform = state.transform,
303 .manualTransform = state.manualTransform,
304 .overscan = state.overscan,
305 .rgbRange = state.rgbRange,
306 .vrrPolicy = state.vrrPolicy,
307 .highDynamicRange = state.highDynamicRange,
308 .sdrBrightness = state.sdrBrightness,
309 .wideColorGamut = state.wideColorGamut,
310 .autoRotationPolicy = state.autoRotation,
311 .iccProfilePath = state.iccProfilePath,
312 .iccProfile = state.iccProfilePath ? IccProfile::load(*state.iccProfilePath) : nullptr,
313 .maxPeakBrightnessOverride = state.maxPeakBrightnessOverride,
314 .maxAverageBrightnessOverride = state.maxAverageBrightnessOverride,
315 .minBrightnessOverride = state.minBrightnessOverride,
316 .sdrGamutWideness = state.sdrGamutWideness,
318 if (setupState.enabled) {
319 priorities.push_back(std::make_pair(output, setupState.priority));
322 std::sort(priorities.begin(), priorities.end(), [](
const auto &left,
const auto &right) {
323 return left.second < right.second;
325 QList<Output *> order;
326 std::transform(priorities.begin(), priorities.end(), std::back_inserter(order), [](
const auto &pair) {
329 return std::make_pair(ret, order);
332std::optional<std::pair<OutputConfiguration, QList<Output *>>> OutputConfigurationStore::generateLidClosedConfig(
const QList<Output *> &outputs)
334 const auto internalIt = std::find_if(
outputs.begin(),
outputs.end(), [](Output *output) {
335 return output->isInternal();
337 if (internalIt ==
outputs.end()) {
340 const auto setup = findSetup(outputs,
false);
344 Output *
const internalOutput = *internalIt;
345 auto [config, order] = setupToConfig(setup->first, setup->second);
346 auto internalChangeset = config.
changeSet(internalOutput);
347 internalChangeset->enabled =
false;
348 order.removeOne(internalOutput);
350 const bool anyEnabled = std::any_of(
outputs.begin(),
outputs.end(), [&config = config](Output *output) {
351 return config.changeSet(output)->enabled.value_or(output->isEnabled());
357 const auto getSize = [](OutputChangeSet *changeset, Output *output) {
358 auto mode = changeset->mode ? changeset->mode->lock() :
nullptr;
360 mode = output->currentMode();
362 const auto scale = changeset->scale.value_or(output->scale());
363 return QSize(std::ceil(mode->size().width() / scale), std::ceil(mode->size().height() / scale));
365 const QPoint internalPos = internalChangeset->pos.value_or(internalOutput->geometry().topLeft());
366 const QSize internalSize = getSize(internalChangeset.get(), internalOutput);
367 for (Output *otherOutput :
outputs) {
368 auto changeset = config.
changeSet(otherOutput);
369 QPoint otherPos = changeset->pos.value_or(otherOutput->geometry().topLeft());
370 if (otherPos.x() >= internalPos.x() + internalSize.width()) {
371 otherPos.rx() -= std::floor(internalSize.width());
373 if (otherPos.y() >= internalPos.y() + internalSize.height()) {
374 otherPos.ry() -= std::floor(internalSize.height());
377 const QSize otherSize = getSize(changeset.get(), otherOutput);
378 const bool overlap = std::any_of(
outputs.begin(),
outputs.end(), [&, &config = config](Output *output) {
379 if (otherOutput == output) {
382 const auto changeset = config.
changeSet(output);
383 const QPoint pos = changeset->pos.value_or(output->geometry().topLeft());
384 return QRect(pos, otherSize).intersects(QRect(otherPos, getSize(changeset.get(), output)));
387 changeset->pos = otherPos;
390 return std::make_pair(config, order);
393std::pair<OutputConfiguration, QList<Output *>> OutputConfigurationStore::generateConfig(
const QList<Output *> &outputs,
bool isLidClosed)
396 if (
const auto closedConfig = generateLidClosedConfig(outputs)) {
397 return *closedConfig;
400 OutputConfiguration ret;
401 QList<Output *> outputOrder;
403 for (
const auto output :
outputs) {
404 const auto outputIndex = findOutput(output, outputs);
405 const bool enable = !isLidClosed || !output->isInternal() ||
outputs.size() == 1;
406 const OutputState existingData = outputIndex ? m_outputs[*outputIndex] : OutputState{};
408 const auto modes = output->modes();
409 const auto modeIt = std::find_if(modes.begin(), modes.end(), [&existingData](
const auto &mode) {
410 return existingData.mode
411 && mode->size() == existingData.mode->size
412 && mode->refreshRate() == existingData.mode->refreshRate;
414 const auto mode = modeIt == modes.end() ? output->currentMode() : *modeIt;
416 const auto changeset = ret.changeSet(output);
421 .scale = existingData.scale.value_or(chooseScale(output, mode.get())),
422 .transform = existingData.transform.value_or(output->panelOrientation()),
423 .manualTransform = existingData.manualTransform.value_or(output->panelOrientation()),
424 .overscan = existingData.overscan.value_or(0),
425 .rgbRange = existingData.rgbRange.value_or(Output::RgbRange::Automatic),
426 .vrrPolicy = existingData.vrrPolicy.value_or(VrrPolicy::Automatic),
427 .highDynamicRange = existingData.highDynamicRange.value_or(
false),
428 .sdrBrightness = existingData.sdrBrightness.value_or(200),
429 .wideColorGamut = existingData.wideColorGamut.value_or(
false),
430 .autoRotationPolicy = existingData.autoRotation.value_or(Output::AutoRotationPolicy::InTabletMode),
433 const auto modeSize = changeset->transform->map(mode->size());
434 pos.setX(std::ceil(pos.x() + modeSize.width() / *changeset->scale));
435 outputOrder.push_back(output);
438 return std::make_pair(ret, outputs);
441std::shared_ptr<OutputMode> OutputConfigurationStore::chooseMode(Output *output)
const
443 const auto modes = output->modes();
447 const auto preferred = std::find_if(modes.begin(), modes.end(), [](
const auto &mode) {
448 return mode->flags() & OutputMode::Flag::Preferred;
450 if (preferred != modes.end()) {
453 std::shared_ptr<OutputMode> highestRefresh = *preferred;
454 for (
const auto &mode : modes) {
455 if (mode->size() == highestRefresh->size() && mode->refreshRate() > highestRefresh->refreshRate()) {
456 highestRefresh = mode;
461 if (highestRefresh->refreshRate() >= 50000) {
462 return highestRefresh;
466 std::shared_ptr<OutputMode> ret;
467 for (
auto mode : modes) {
468 if (mode->flags() & OutputMode::Flag::Generated) {
476 const bool retUsableRefreshRate = ret->refreshRate() >= 50000;
477 const bool usableRefreshRate = mode->refreshRate() >= 50000;
478 if (retUsableRefreshRate && !usableRefreshRate) {
482 if ((usableRefreshRate && !retUsableRefreshRate)
483 || mode->size().width() > ret->size().width()
484 || mode->size().height() > ret->size().height()
485 || (mode->size() == ret->size() && mode->refreshRate() > ret->refreshRate())) {
492double OutputConfigurationStore::chooseScale(Output *output, OutputMode *mode)
const
494 if (output->physicalSize().height() <= 0) {
498 const double outputDpi = mode->size().height() / (output->physicalSize().height() / 25.4);
499 const double desiredScale = outputDpi / targetDpi(output);
501 return std::clamp(std::round(100.0 * desiredScale / 25.0) * 25.0 / 100.0, 1.0, 3.0);
504double OutputConfigurationStore::targetDpi(Output *output)
const
513 const bool hasLaptopLid = std::any_of(devices.begin(), devices.end(), [](
const auto &device) {
514 return device->isLidSwitch();
516 if (output->isInternal()) {
530void OutputConfigurationStore::load()
532 const QString jsonPath = QStandardPaths::locate(QStandardPaths::ConfigLocation, QStringLiteral(
"kwinoutputconfig.json"));
533 if (jsonPath.isEmpty()) {
538 if (!f.open(QIODevice::ReadOnly)) {
539 qCWarning(KWIN_CORE) <<
"Could not open file" << jsonPath;
542 QJsonParseError error;
543 const auto doc = QJsonDocument::fromJson(f.readAll(), &error);
544 if (error.error != QJsonParseError::NoError) {
545 qCWarning(KWIN_CORE) <<
"Failed to parse" << jsonPath << error.errorString();
548 const auto array = doc.array();
549 std::vector<QJsonObject> objects;
550 std::transform(array.begin(), array.end(), std::back_inserter(objects), [](
const auto &json) {
551 return json.toObject();
553 const auto outputsIt = std::find_if(objects.begin(), objects.end(), [](
const auto &obj) {
554 return obj[
"name"].toString() ==
"outputs" && obj[
"data"].isArray();
556 const auto setupsIt = std::find_if(objects.begin(), objects.end(), [](
const auto &obj) {
557 return obj[
"name"].toString() ==
"setups" && obj[
"data"].isArray();
559 if (outputsIt == objects.end() || setupsIt == objects.end()) {
562 const auto outputs = (*outputsIt)[
"data"].toArray();
564 std::vector<std::optional<OutputState>> outputDatas;
565 for (
const auto &output :
outputs) {
566 const auto data = output.toObject();
568 bool hasIdentifier =
false;
569 if (
const auto it = data.find(
"edidIdentifier"); it != data.end()) {
570 if (
const auto str = it->toString(); !str.isEmpty()) {
571 state.edidIdentifier = str;
572 hasIdentifier =
true;
575 if (
const auto it = data.find(
"edidHash"); it != data.end()) {
576 if (
const auto str = it->toString(); !str.isEmpty()) {
577 state.edidHash = str;
578 hasIdentifier =
true;
581 if (
const auto it = data.find(
"connectorName"); it != data.end()) {
582 if (
const auto str = it->toString(); !str.isEmpty()) {
583 state.connectorName = str;
584 hasIdentifier =
true;
587 if (
const auto it = data.find(
"mstPath"); it != data.end()) {
588 if (
const auto str = it->toString(); !str.isEmpty()) {
590 hasIdentifier =
true;
593 if (!hasIdentifier) {
596 outputDatas.push_back(std::nullopt);
597 qCWarning(KWIN_CORE,
"Output in config is missing identifiers");
600 const bool hasDuplicate = std::any_of(outputDatas.begin(), outputDatas.end(), [&state](
const auto &data) {
602 && data->edidIdentifier == state.edidIdentifier
603 && data->edidHash == state.edidHash
604 && data->mstPath == state.mstPath
605 && data->connectorName == state.connectorName;
608 qCWarning(KWIN_CORE) <<
"Duplicate output found in config for edidIdentifier:" << state.edidIdentifier.value_or(
"<empty>") <<
"; connectorName:" << state.connectorName.value_or(
"<empty>") <<
"; mstPath:" << state.mstPath;
609 outputDatas.push_back(std::nullopt);
612 if (
const auto it = data.find(
"mode"); it != data.end()) {
613 const auto obj = it->toObject();
614 const int width = obj[
"width"].toInt(0);
615 const int height = obj[
"height"].toInt(0);
616 const int refreshRate = obj[
"refreshRate"].toInt(0);
617 if (width > 0 && height > 0 && refreshRate > 0) {
618 state.mode = ModeData{
619 .size = QSize(width, height),
620 .refreshRate = uint32_t(refreshRate),
624 if (
const auto it = data.find(
"scale"); it != data.end()) {
625 const double scale = it->toDouble(0);
626 if (scale > 0 && scale <= 3) {
630 if (
const auto it = data.find(
"transform"); it != data.end()) {
631 const auto str = it->toString();
632 if (str ==
"Normal") {
633 state.transform = state.manualTransform = OutputTransform::Kind::Normal;
634 }
else if (str ==
"Rotated90") {
635 state.transform = state.manualTransform = OutputTransform::Kind::Rotate90;
636 }
else if (str ==
"Rotated180") {
637 state.transform = state.manualTransform = OutputTransform::Kind::Rotate180;
638 }
else if (str ==
"Rotated270") {
639 state.transform = state.manualTransform = OutputTransform::Kind::Rotate270;
640 }
else if (str ==
"Flipped") {
641 state.transform = state.manualTransform = OutputTransform::Kind::FlipX;
642 }
else if (str ==
"Flipped90") {
643 state.transform = state.manualTransform = OutputTransform::Kind::FlipX90;
644 }
else if (str ==
"Flipped180") {
645 state.transform = state.manualTransform = OutputTransform::Kind::FlipX180;
646 }
else if (str ==
"Flipped270") {
647 state.transform = state.manualTransform = OutputTransform::Kind::FlipX270;
650 if (
const auto it = data.find(
"overscan"); it != data.end()) {
651 const int overscan = it->toInt(-1);
652 if (overscan >= 0 && overscan <= 100) {
653 state.overscan = overscan;
656 if (
const auto it = data.find(
"rgbRange"); it != data.end()) {
657 const auto str = it->toString();
658 if (str ==
"Automatic") {
659 state.rgbRange = Output::RgbRange::Automatic;
660 }
else if (str ==
"Limited") {
661 state.rgbRange = Output::RgbRange::Limited;
662 }
else if (str ==
"Full") {
663 state.rgbRange = Output::RgbRange::Full;
666 if (
const auto it = data.find(
"vrrPolicy"); it != data.end()) {
667 const auto str = it->toString();
668 if (str ==
"Never") {
669 state.vrrPolicy = VrrPolicy::Never;
670 }
else if (str ==
"Automatic") {
671 state.vrrPolicy = VrrPolicy::Automatic;
672 }
else if (str ==
"Always") {
673 state.vrrPolicy = VrrPolicy::Always;
676 if (
const auto it = data.find(
"highDynamicRange"); it != data.end() && it->isBool()) {
677 state.highDynamicRange = it->toBool();
679 if (
const auto it = data.find(
"sdrBrightness"); it != data.end() && it->isDouble()) {
680 state.sdrBrightness = it->toInt(200);
682 if (
const auto it = data.find(
"wideColorGamut"); it != data.end() && it->isBool()) {
683 state.wideColorGamut = it->toBool();
685 if (
const auto it = data.find(
"autoRotation"); it != data.end()) {
686 const auto str = it->toString();
687 if (str ==
"Never") {
688 state.autoRotation = Output::AutoRotationPolicy::Never;
689 }
else if (str ==
"InTabletMode") {
690 state.autoRotation = Output::AutoRotationPolicy::InTabletMode;
691 }
else if (str ==
"Always") {
692 state.autoRotation = Output::AutoRotationPolicy::Always;
695 if (
const auto it = data.find(
"iccProfilePath"); it != data.end()) {
696 state.iccProfilePath = it->toString();
698 if (
const auto it = data.find(
"maxPeakBrightnessOverride"); it != data.end() && it->isDouble()) {
699 state.maxPeakBrightnessOverride = it->toDouble();
701 if (
const auto it = data.find(
"maxAverageBrightnessOverride"); it != data.end() && it->isDouble()) {
702 state.maxAverageBrightnessOverride = it->toDouble();
704 if (
const auto it = data.find(
"minBrightnessOverride"); it != data.end() && it->isDouble()) {
705 state.minBrightnessOverride = it->toDouble();
707 if (
const auto it = data.find(
"sdrGamutWideness"); it != data.end() && it->isDouble()) {
708 state.sdrGamutWideness = it->toDouble();
710 outputDatas.push_back(state);
713 const auto setups = (*setupsIt)[
"data"].toArray();
714 for (
const auto &s : setups) {
715 const auto data = s.toObject();
716 const auto outputs = data[
"outputs"].toArray();
719 for (
const auto &output :
outputs) {
720 const auto outputData = output.toObject();
722 if (
const auto it = outputData.find(
"enabled"); it != outputData.end() && it->isBool()) {
723 state.enabled = it->toBool();
728 if (
const auto it = outputData.find(
"outputIndex"); it != outputData.end()) {
729 const int index = it->toInt(-1);
730 if (index <= -1 ||
size_t(index) >= outputDatas.size()) {
735 const bool unique = std::none_of(setup.outputs.begin(), setup.outputs.end(), [&index](
const auto &output) {
736 return output.outputIndex == size_t(index);
742 state.outputIndex = index;
744 if (
const auto it = outputData.find(
"position"); it != outputData.end()) {
745 const auto obj = it->toObject();
746 const auto x = obj.find(
"x");
747 const auto y = obj.find(
"y");
748 if (x == obj.end() || !x->isDouble() || y == obj.end() || !y->isDouble()) {
752 state.position = QPoint(x->toInt(0), y->toInt(0));
757 if (
const auto it = outputData.find(
"priority"); it != outputData.end()) {
758 state.priority = it->toInt(-1);
759 if (state.priority < 0 && state.enabled) {
764 setup.outputs.push_back(state);
766 if (fail || setup.outputs.empty()) {
770 const bool noneEnabled = std::none_of(setup.outputs.begin(), setup.outputs.end(), [](
const auto &output) {
771 return output.enabled;
776 setup.lidClosed = data[
"lidClosed"].toBool(
false);
778 const bool alreadyExists = std::any_of(m_setups.begin(), m_setups.end(), [&setup](
const auto &other) {
779 if (setup.lidClosed != other.lidClosed || setup.outputs.size() != other.outputs.size()) {
782 return std::all_of(setup.outputs.begin(), setup.outputs.end(), [&other](
const auto &output) {
783 return std::any_of(other.outputs.begin(), other.outputs.end(), [&output](const auto &otherOutput) {
784 return output.outputIndex == otherOutput.outputIndex;
791 m_setups.push_back(setup);
795 for (
size_t i = 0; i < outputDatas.size();) {
796 if (!outputDatas[i]) {
797 outputDatas.erase(outputDatas.begin() + i);
798 for (
auto setupIt = m_setups.begin(); setupIt != m_setups.end();) {
799 const bool broken = std::any_of(setupIt->outputs.begin(), setupIt->outputs.end(), [i](
const auto &output) {
800 return output.outputIndex == i;
803 setupIt = m_setups.erase(setupIt);
806 for (
auto &output : setupIt->
outputs) {
807 if (output.outputIndex > i) {
808 output.outputIndex--;
818 for (
const auto &o : outputDatas) {
820 m_outputs.push_back(*o);
824void OutputConfigurationStore::save()
826 QJsonDocument document;
830 QJsonArray outputsData;
831 for (
const auto &output : m_outputs) {
833 if (output.edidIdentifier) {
834 o[
"edidIdentifier"] = *output.edidIdentifier;
836 if (!output.edidHash.isEmpty()) {
837 o[
"edidHash"] = output.edidHash;
839 if (output.connectorName) {
840 o[
"connectorName"] = *output.connectorName;
842 if (!output.mstPath.isEmpty()) {
843 o[
"mstPath"] = output.mstPath;
847 mode[
"width"] = output.mode->size.width();
848 mode[
"height"] = output.mode->size.height();
849 mode[
"refreshRate"] = int(output.mode->refreshRate);
853 o[
"scale"] = *output.scale;
855 if (output.manualTransform == OutputTransform::Kind::Normal) {
856 o[
"transform"] =
"Normal";
857 }
else if (output.manualTransform == OutputTransform::Kind::Rotate90) {
858 o[
"transform"] =
"Rotated90";
859 }
else if (output.manualTransform == OutputTransform::Kind::Rotate180) {
860 o[
"transform"] =
"Rotated180";
861 }
else if (output.manualTransform == OutputTransform::Kind::Rotate270) {
862 o[
"transform"] =
"Rotated270";
863 }
else if (output.manualTransform == OutputTransform::Kind::FlipX) {
864 o[
"transform"] =
"Flipped";
865 }
else if (output.manualTransform == OutputTransform::Kind::FlipX90) {
866 o[
"transform"] =
"Flipped90";
867 }
else if (output.manualTransform == OutputTransform::Kind::FlipX180) {
868 o[
"transform"] =
"Flipped180";
869 }
else if (output.manualTransform == OutputTransform::Kind::FlipX270) {
870 o[
"transform"] =
"Flipped270";
872 if (output.overscan) {
873 o[
"overscan"] = int(*output.overscan);
875 if (output.rgbRange == Output::RgbRange::Automatic) {
876 o[
"rgbRange"] =
"Automatic";
877 }
else if (output.rgbRange == Output::RgbRange::Limited) {
878 o[
"rgbRange"] =
"Limited";
879 }
else if (output.rgbRange == Output::RgbRange::Full) {
880 o[
"rgbRange"] =
"Full";
882 if (output.vrrPolicy == VrrPolicy::Never) {
883 o[
"vrrPolicy"] =
"Never";
884 }
else if (output.vrrPolicy == VrrPolicy::Automatic) {
885 o[
"vrrPolicy"] =
"Automatic";
886 }
else if (output.vrrPolicy == VrrPolicy::Always) {
887 o[
"vrrPolicy"] =
"Always";
889 if (output.highDynamicRange) {
890 o[
"highDynamicRange"] = *output.highDynamicRange;
892 if (output.sdrBrightness) {
893 o[
"sdrBrightness"] = int(*output.sdrBrightness);
895 if (output.wideColorGamut) {
896 o[
"wideColorGamut"] = *output.wideColorGamut;
898 if (output.autoRotation) {
899 switch (*output.autoRotation) {
900 case Output::AutoRotationPolicy::Never:
901 o[
"autoRotation"] =
"Never";
903 case Output::AutoRotationPolicy::InTabletMode:
904 o[
"autoRotation"] =
"InTabletMode";
906 case Output::AutoRotationPolicy::Always:
907 o[
"autoRotation"] =
"Always";
911 if (output.iccProfilePath) {
912 o[
"iccProfilePath"] = *output.iccProfilePath;
914 if (output.maxPeakBrightnessOverride) {
915 o[
"maxPeakBrightnessOverride"] = *output.maxPeakBrightnessOverride;
917 if (output.maxAverageBrightnessOverride) {
918 o[
"maxAverageBrightnessOverride"] = *output.maxAverageBrightnessOverride;
920 if (output.minBrightnessOverride) {
921 o[
"minBrightnessOverride"] = *output.minBrightnessOverride;
923 if (output.sdrGamutWideness) {
924 o[
"sdrGamutWideness"] = *output.sdrGamutWideness;
926 outputsData.append(o);
929 array.append(outputs);
932 setups[
"name"] =
"setups";
933 QJsonArray setupData;
934 for (
const auto &setup : m_setups) {
936 o[
"lidClosed"] = setup.lidClosed;
938 for (ssize_t i = 0; i < setup.outputs.size(); i++) {
939 const auto &output = setup.outputs[i];
941 o[
"enabled"] = output.enabled;
942 o[
"outputIndex"] = int(output.outputIndex);
943 o[
"priority"] = output.priority;
945 pos[
"x"] = output.position.x();
946 pos[
"y"] = output.position.y();
955 setups[
"data"] = setupData;
956 array.append(setups);
958 const QString path = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) +
"/kwinoutputconfig.json";
960 if (!f.open(QIODevice::WriteOnly)) {
961 qCWarning(KWIN_CORE,
"Couldn't open output config file %s", qPrintable(path));
964 document.setArray(array);
965 f.write(document.toJson());
969bool OutputConfigurationStore::isAutoRotateActive(
const QList<Output *> &outputs,
bool isTabletMode)
const
971 const auto internalIt = std::find_if(outputs.begin(), outputs.end(), [](
Output *output) {
972 return output->isInternal() && output->isEnabled();
974 if (internalIt == outputs.end()) {
977 Output *internal = *internalIt;
979 case Output::AutoRotationPolicy::Never:
981 case Output::AutoRotationPolicy::InTabletMode:
983 case Output::AutoRotationPolicy::Always:
QByteArray identifier() const
std::optional< std::weak_ptr< OutputMode > > mode
std::shared_ptr< OutputChangeSet > constChangeSet(Output *output) const
std::shared_ptr< OutputChangeSet > changeSet(Output *output)
OutputConfigurationStore()
void storeConfig(const QList< Output * > &allOutputs, bool isLidClosed, const OutputConfiguration &config, const QList< Output * > &outputOrder)
std::optional< std::tuple< OutputConfiguration, QList< Output * >, ConfigType > > queryConfig(const QList< Output * > &outputs, bool isLidClosed, QOrientationReading *orientation, bool isTabletMode)
bool isAutoRotateActive(const QList< Output * > &outputs, bool isTabletMode) const
~OutputConfigurationStore()
VrrPolicy vrrPolicy() const
std::shared_ptr< OutputMode > currentMode() const
double sdrGamutWideness() const
uint32_t overscan() const
bool highDynamicRange() const
std::optional< double > maxAverageBrightnessOverride() const
RgbRange rgbRange() const
OutputTransform transform() const
AutoRotationPolicy autoRotationPolicy() const
QString iccProfilePath() const
QByteArray mstPath() const
uint32_t sdrBrightness() const
const Edid & edid() const
OutputTransform manualTransform() const
bool wideColorGamut() const
std::optional< double > minBrightnessOverride() const
std::optional< double > maxPeakBrightnessOverride() const
QString connectedOutputsHash(const QList< Output * > &outputs, bool isLidClosed)
See KScreen::Config::connectedOutputsHash in libkscreen.
std::optional< std::pair< OutputConfiguration, QList< Output * > > > readOutputConfig(const QList< Output * > &outputs, const QString &hash)
QList< KWayland::Client::Output * > outputs
InputRedirection * input()