File indexing completed on 2024-04-28 05:30:26

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "outputconfigurationstore.h"
0010 #include "core/iccprofile.h"
0011 #include "core/inputdevice.h"
0012 #include "core/output.h"
0013 #include "core/outputbackend.h"
0014 #include "core/outputconfiguration.h"
0015 #include "input.h"
0016 #include "input_event.h"
0017 #include "kscreenintegration.h"
0018 #include "workspace.h"
0019 
0020 #include <QFile>
0021 #include <QJsonArray>
0022 #include <QJsonDocument>
0023 #include <QJsonObject>
0024 #include <QOrientationReading>
0025 
0026 namespace KWin
0027 {
0028 
0029 OutputConfigurationStore::OutputConfigurationStore()
0030 {
0031     load();
0032 }
0033 
0034 OutputConfigurationStore::~OutputConfigurationStore()
0035 {
0036     save();
0037 }
0038 
0039 std::optional<std::tuple<OutputConfiguration, QList<Output *>, OutputConfigurationStore::ConfigType>> OutputConfigurationStore::queryConfig(const QList<Output *> &outputs, bool isLidClosed, QOrientationReading *orientation, bool isTabletMode)
0040 {
0041     QList<Output *> relevantOutputs;
0042     std::copy_if(outputs.begin(), outputs.end(), std::back_inserter(relevantOutputs), [](Output *output) {
0043         return !output->isNonDesktop() && !output->isPlaceholder();
0044     });
0045     if (relevantOutputs.isEmpty()) {
0046         return std::nullopt;
0047     }
0048     if (const auto opt = findSetup(relevantOutputs, isLidClosed)) {
0049         const auto &[setup, outputStates] = *opt;
0050         auto [config, order] = setupToConfig(setup, outputStates);
0051         applyOrientationReading(config, relevantOutputs, orientation, isTabletMode);
0052         storeConfig(relevantOutputs, isLidClosed, config, order);
0053         return std::make_tuple(config, order, ConfigType::Preexisting);
0054     }
0055     if (auto kscreenConfig = KScreenIntegration::readOutputConfig(relevantOutputs, KScreenIntegration::connectedOutputsHash(relevantOutputs, isLidClosed))) {
0056         auto &[config, order] = *kscreenConfig;
0057         applyOrientationReading(config, relevantOutputs, orientation, isTabletMode);
0058         storeConfig(relevantOutputs, isLidClosed, config, order);
0059         return std::make_tuple(config, order, ConfigType::Preexisting);
0060     }
0061     auto [config, order] = generateConfig(relevantOutputs, isLidClosed);
0062     applyOrientationReading(config, relevantOutputs, orientation, isTabletMode);
0063     storeConfig(relevantOutputs, isLidClosed, config, order);
0064     return std::make_tuple(config, order, ConfigType::Generated);
0065 }
0066 
0067 void OutputConfigurationStore::applyOrientationReading(OutputConfiguration &config, const QList<Output *> &outputs, QOrientationReading *orientation, bool isTabletMode)
0068 {
0069     const auto output = std::find_if(outputs.begin(), outputs.end(), [&config](Output *output) {
0070         return output->isInternal() && config.changeSet(output)->enabled.value_or(output->isEnabled());
0071     });
0072     if (output == outputs.end()) {
0073         return;
0074     }
0075     // TODO move other outputs to matching positions
0076     const auto changeset = config.changeSet(*output);
0077     if (!isAutoRotateActive(outputs, isTabletMode)) {
0078         changeset->transform = changeset->manualTransform;
0079         return;
0080     }
0081     const auto panelOrientation = (*output)->panelOrientation();
0082     switch (orientation->orientation()) {
0083     case QOrientationReading::Orientation::TopUp:
0084         changeset->transform = panelOrientation;
0085         return;
0086     case QOrientationReading::Orientation::TopDown:
0087         changeset->transform = panelOrientation.combine(OutputTransform::Kind::Rotate180);
0088         return;
0089     case QOrientationReading::Orientation::LeftUp:
0090         changeset->transform = panelOrientation.combine(OutputTransform::Kind::Rotate90);
0091         return;
0092     case QOrientationReading::Orientation::RightUp:
0093         changeset->transform = panelOrientation.combine(OutputTransform::Kind::Rotate270);
0094         return;
0095     case QOrientationReading::Orientation::FaceUp:
0096     case QOrientationReading::Orientation::FaceDown:
0097         return;
0098     case QOrientationReading::Orientation::Undefined:
0099         changeset->transform = changeset->manualTransform;
0100         return;
0101     }
0102 }
0103 
0104 std::optional<std::pair<OutputConfigurationStore::Setup *, std::unordered_map<Output *, size_t>>> OutputConfigurationStore::findSetup(const QList<Output *> &outputs, bool lidClosed)
0105 {
0106     std::unordered_map<Output *, size_t> outputStates;
0107     for (Output *output : outputs) {
0108         if (auto opt = findOutput(output, outputs)) {
0109             outputStates[output] = *opt;
0110         } else {
0111             return std::nullopt;
0112         }
0113     }
0114     const auto setup = std::find_if(m_setups.begin(), m_setups.end(), [lidClosed, &outputStates](const auto &setup) {
0115         if (setup.lidClosed != lidClosed || size_t(setup.outputs.size()) != outputStates.size()) {
0116             return false;
0117         }
0118         return std::all_of(outputStates.begin(), outputStates.end(), [&setup](const auto &outputIt) {
0119             return std::any_of(setup.outputs.begin(), setup.outputs.end(), [&outputIt](const auto &outputInfo) {
0120                 return outputInfo.outputIndex == outputIt.second;
0121             });
0122         });
0123     });
0124     if (setup == m_setups.end()) {
0125         return std::nullopt;
0126     } else {
0127         return std::make_pair(&(*setup), outputStates);
0128     }
0129 }
0130 
0131 std::optional<size_t> OutputConfigurationStore::findOutput(Output *output, const QList<Output *> &allOutputs) const
0132 {
0133     const bool uniqueEdid = !output->edid().identifier().isEmpty() && std::none_of(allOutputs.begin(), allOutputs.end(), [output](Output *otherOutput) {
0134         return otherOutput != output && otherOutput->edid().identifier() == output->edid().identifier();
0135     });
0136     const bool uniqueEdidHash = !output->edid().hash().isEmpty() && std::none_of(allOutputs.begin(), allOutputs.end(), [output](Output *otherOutput) {
0137         return otherOutput != output && otherOutput->edid().hash() == output->edid().hash();
0138     });
0139     const bool uniqueMst = !output->mstPath().isEmpty() && std::none_of(allOutputs.begin(), allOutputs.end(), [output](Output *otherOutput) {
0140         return otherOutput != output && otherOutput->edid().identifier() == output->edid().identifier() && otherOutput->mstPath() == output->mstPath();
0141     });
0142     auto it = std::find_if(m_outputs.begin(), m_outputs.end(), [&](const auto &outputState) {
0143         if (output->edid().isValid()) {
0144             if (outputState.edidIdentifier != output->edid().identifier()) {
0145                 return false;
0146             } else if (uniqueEdid) {
0147                 return true;
0148             }
0149         }
0150         if (!output->edid().hash().isEmpty()) {
0151             if (outputState.edidHash != output->edid().hash()) {
0152                 return false;
0153             } else if (uniqueEdidHash) {
0154                 return true;
0155             }
0156         }
0157         if (outputState.mstPath != output->mstPath()) {
0158             return false;
0159         } else if (uniqueMst) {
0160             return true;
0161         }
0162         return outputState.connectorName == output->name();
0163     });
0164     if (it == m_outputs.end() && uniqueEdidHash) {
0165         // handle the edge case of EDID parsing failing in the past but not failing anymore
0166         it = std::find_if(m_outputs.begin(), m_outputs.end(), [&](const auto &outputState) {
0167             return outputState.edidHash == output->edid().hash();
0168         });
0169     }
0170     if (it != m_outputs.end()) {
0171         return std::distance(m_outputs.begin(), it);
0172     } else {
0173         return std::nullopt;
0174     }
0175 }
0176 
0177 void OutputConfigurationStore::storeConfig(const QList<Output *> &allOutputs, bool isLidClosed, const OutputConfiguration &config, const QList<Output *> &outputOrder)
0178 {
0179     QList<Output *> relevantOutputs;
0180     std::copy_if(allOutputs.begin(), allOutputs.end(), std::back_inserter(relevantOutputs), [](Output *output) {
0181         return !output->isNonDesktop() && !output->isPlaceholder();
0182     });
0183     if (relevantOutputs.isEmpty()) {
0184         return;
0185     }
0186     const auto opt = findSetup(relevantOutputs, isLidClosed);
0187     Setup *setup = nullptr;
0188     if (opt) {
0189         setup = opt->first;
0190     } else {
0191         m_setups.push_back(Setup{});
0192         setup = &m_setups.back();
0193         setup->lidClosed = isLidClosed;
0194     }
0195     for (Output *output : relevantOutputs) {
0196         auto outputIndex = findOutput(output, outputOrder);
0197         if (!outputIndex) {
0198             m_outputs.push_back(OutputState{});
0199             outputIndex = m_outputs.size() - 1;
0200         }
0201         auto outputIt = std::find_if(setup->outputs.begin(), setup->outputs.end(), [outputIndex](const auto &output) {
0202             return output.outputIndex == outputIndex;
0203         });
0204         if (outputIt == setup->outputs.end()) {
0205             setup->outputs.push_back(SetupState{});
0206             outputIt = setup->outputs.end() - 1;
0207         }
0208         if (const auto changeSet = config.constChangeSet(output)) {
0209             std::shared_ptr<OutputMode> mode = changeSet->mode.value_or(output->currentMode()).lock();
0210             if (!mode) {
0211                 mode = output->currentMode();
0212             }
0213             m_outputs[*outputIndex] = OutputState{
0214                 .edidIdentifier = output->edid().identifier(),
0215                 .connectorName = output->name(),
0216                 .edidHash = output->edid().isValid() ? output->edid().hash() : QString{},
0217                 .mstPath = output->mstPath(),
0218                 .mode = ModeData{
0219                     .size = mode->size(),
0220                     .refreshRate = mode->refreshRate(),
0221                 },
0222                 .scale = changeSet->scale.value_or(output->scale()),
0223                 .transform = changeSet->transform.value_or(output->transform()),
0224                 .manualTransform = changeSet->manualTransform.value_or(output->manualTransform()),
0225                 .overscan = changeSet->overscan.value_or(output->overscan()),
0226                 .rgbRange = changeSet->rgbRange.value_or(output->rgbRange()),
0227                 .vrrPolicy = changeSet->vrrPolicy.value_or(output->vrrPolicy()),
0228                 .highDynamicRange = changeSet->highDynamicRange.value_or(output->highDynamicRange()),
0229                 .sdrBrightness = changeSet->sdrBrightness.value_or(output->sdrBrightness()),
0230                 .wideColorGamut = changeSet->wideColorGamut.value_or(output->wideColorGamut()),
0231                 .autoRotation = changeSet->autoRotationPolicy.value_or(output->autoRotationPolicy()),
0232                 .iccProfilePath = changeSet->iccProfilePath.value_or(output->iccProfilePath()),
0233                 .maxPeakBrightnessOverride = changeSet->maxPeakBrightnessOverride.value_or(output->maxPeakBrightnessOverride()),
0234                 .maxAverageBrightnessOverride = changeSet->maxAverageBrightnessOverride.value_or(output->maxAverageBrightnessOverride()),
0235                 .minBrightnessOverride = changeSet->minBrightnessOverride.value_or(output->minBrightnessOverride()),
0236                 .sdrGamutWideness = changeSet->sdrGamutWideness.value_or(output->sdrGamutWideness()),
0237             };
0238             *outputIt = SetupState{
0239                 .outputIndex = *outputIndex,
0240                 .position = changeSet->pos.value_or(output->geometry().topLeft()),
0241                 .enabled = changeSet->enabled.value_or(output->isEnabled()),
0242                 .priority = int(outputOrder.indexOf(output)),
0243             };
0244         } else {
0245             const auto mode = output->currentMode();
0246             m_outputs[*outputIndex] = OutputState{
0247                 .edidIdentifier = output->edid().identifier(),
0248                 .connectorName = output->name(),
0249                 .edidHash = output->edid().isValid() ? output->edid().hash() : QString{},
0250                 .mstPath = output->mstPath(),
0251                 .mode = ModeData{
0252                     .size = mode->size(),
0253                     .refreshRate = mode->refreshRate(),
0254                 },
0255                 .scale = output->scale(),
0256                 .transform = output->transform(),
0257                 .manualTransform = output->manualTransform(),
0258                 .overscan = output->overscan(),
0259                 .rgbRange = output->rgbRange(),
0260                 .vrrPolicy = output->vrrPolicy(),
0261                 .highDynamicRange = output->highDynamicRange(),
0262                 .sdrBrightness = output->sdrBrightness(),
0263                 .wideColorGamut = output->wideColorGamut(),
0264                 .autoRotation = output->autoRotationPolicy(),
0265                 .iccProfilePath = output->iccProfilePath(),
0266                 .maxPeakBrightnessOverride = output->maxPeakBrightnessOverride(),
0267                 .maxAverageBrightnessOverride = output->maxAverageBrightnessOverride(),
0268                 .minBrightnessOverride = output->minBrightnessOverride(),
0269                 .sdrGamutWideness = output->sdrGamutWideness(),
0270             };
0271             *outputIt = SetupState{
0272                 .outputIndex = *outputIndex,
0273                 .position = output->geometry().topLeft(),
0274                 .enabled = output->isEnabled(),
0275                 .priority = int(outputOrder.indexOf(output)),
0276             };
0277         }
0278     }
0279     save();
0280 }
0281 
0282 std::pair<OutputConfiguration, QList<Output *>> OutputConfigurationStore::setupToConfig(Setup *setup, const std::unordered_map<Output *, size_t> &outputMap) const
0283 {
0284     OutputConfiguration ret;
0285     QList<std::pair<Output *, size_t>> priorities;
0286     for (const auto &[output, outputIndex] : outputMap) {
0287         const OutputState &state = m_outputs[outputIndex];
0288         const auto &setupState = *std::find_if(setup->outputs.begin(), setup->outputs.end(), [outputIndex = outputIndex](const auto &state) {
0289             return state.outputIndex == outputIndex;
0290         });
0291         const auto modes = output->modes();
0292         const auto mode = std::find_if(modes.begin(), modes.end(), [&state](const auto &mode) {
0293             return state.mode
0294                 && mode->size() == state.mode->size
0295                 && mode->refreshRate() == state.mode->refreshRate;
0296         });
0297         *ret.changeSet(output) = OutputChangeSet{
0298             .mode = mode == modes.end() ? std::nullopt : std::optional(*mode),
0299             .enabled = setupState.enabled,
0300             .pos = setupState.position,
0301             .scale = state.scale,
0302             .transform = state.transform,
0303             .manualTransform = state.manualTransform,
0304             .overscan = state.overscan,
0305             .rgbRange = state.rgbRange,
0306             .vrrPolicy = state.vrrPolicy,
0307             .highDynamicRange = state.highDynamicRange,
0308             .sdrBrightness = state.sdrBrightness,
0309             .wideColorGamut = state.wideColorGamut,
0310             .autoRotationPolicy = state.autoRotation,
0311             .iccProfilePath = state.iccProfilePath,
0312             .iccProfile = state.iccProfilePath ? IccProfile::load(*state.iccProfilePath) : nullptr,
0313             .maxPeakBrightnessOverride = state.maxPeakBrightnessOverride,
0314             .maxAverageBrightnessOverride = state.maxAverageBrightnessOverride,
0315             .minBrightnessOverride = state.minBrightnessOverride,
0316             .sdrGamutWideness = state.sdrGamutWideness,
0317         };
0318         if (setupState.enabled) {
0319             priorities.push_back(std::make_pair(output, setupState.priority));
0320         }
0321     }
0322     std::sort(priorities.begin(), priorities.end(), [](const auto &left, const auto &right) {
0323         return left.second < right.second;
0324     });
0325     QList<Output *> order;
0326     std::transform(priorities.begin(), priorities.end(), std::back_inserter(order), [](const auto &pair) {
0327         return pair.first;
0328     });
0329     return std::make_pair(ret, order);
0330 }
0331 
0332 std::optional<std::pair<OutputConfiguration, QList<Output *>>> OutputConfigurationStore::generateLidClosedConfig(const QList<Output *> &outputs)
0333 {
0334     const auto internalIt = std::find_if(outputs.begin(), outputs.end(), [](Output *output) {
0335         return output->isInternal();
0336     });
0337     if (internalIt == outputs.end()) {
0338         return std::nullopt;
0339     }
0340     const auto setup = findSetup(outputs, false);
0341     if (!setup) {
0342         return std::nullopt;
0343     }
0344     Output *const internalOutput = *internalIt;
0345     auto [config, order] = setupToConfig(setup->first, setup->second);
0346     auto internalChangeset = config.changeSet(internalOutput);
0347     internalChangeset->enabled = false;
0348     order.removeOne(internalOutput);
0349 
0350     const bool anyEnabled = std::any_of(outputs.begin(), outputs.end(), [&config = config](Output *output) {
0351         return config.changeSet(output)->enabled.value_or(output->isEnabled());
0352     });
0353     if (!anyEnabled) {
0354         return std::nullopt;
0355     }
0356 
0357     const auto getSize = [](OutputChangeSet *changeset, Output *output) {
0358         auto mode = changeset->mode ? changeset->mode->lock() : nullptr;
0359         if (!mode) {
0360             mode = output->currentMode();
0361         }
0362         const auto scale = changeset->scale.value_or(output->scale());
0363         return QSize(std::ceil(mode->size().width() / scale), std::ceil(mode->size().height() / scale));
0364     };
0365     const QPoint internalPos = internalChangeset->pos.value_or(internalOutput->geometry().topLeft());
0366     const QSize internalSize = getSize(internalChangeset.get(), internalOutput);
0367     for (Output *otherOutput : outputs) {
0368         auto changeset = config.changeSet(otherOutput);
0369         QPoint otherPos = changeset->pos.value_or(otherOutput->geometry().topLeft());
0370         if (otherPos.x() >= internalPos.x() + internalSize.width()) {
0371             otherPos.rx() -= std::floor(internalSize.width());
0372         }
0373         if (otherPos.y() >= internalPos.y() + internalSize.height()) {
0374             otherPos.ry() -= std::floor(internalSize.height());
0375         }
0376         // make sure this doesn't make outputs overlap, which is neither supported nor expected by users
0377         const QSize otherSize = getSize(changeset.get(), otherOutput);
0378         const bool overlap = std::any_of(outputs.begin(), outputs.end(), [&, &config = config](Output *output) {
0379             if (otherOutput == output) {
0380                 return false;
0381             }
0382             const auto changeset = config.changeSet(output);
0383             const QPoint pos = changeset->pos.value_or(output->geometry().topLeft());
0384             return QRect(pos, otherSize).intersects(QRect(otherPos, getSize(changeset.get(), output)));
0385         });
0386         if (!overlap) {
0387             changeset->pos = otherPos;
0388         }
0389     }
0390     return std::make_pair(config, order);
0391 }
0392 
0393 std::pair<OutputConfiguration, QList<Output *>> OutputConfigurationStore::generateConfig(const QList<Output *> &outputs, bool isLidClosed)
0394 {
0395     if (isLidClosed) {
0396         if (const auto closedConfig = generateLidClosedConfig(outputs)) {
0397             return *closedConfig;
0398         }
0399     }
0400     OutputConfiguration ret;
0401     QList<Output *> outputOrder;
0402     QPoint pos(0, 0);
0403     for (const auto output : outputs) {
0404         const auto outputIndex = findOutput(output, outputs);
0405         const bool enable = !isLidClosed || !output->isInternal() || outputs.size() == 1;
0406         const OutputState existingData = outputIndex ? m_outputs[*outputIndex] : OutputState{};
0407 
0408         const auto modes = output->modes();
0409         const auto modeIt = std::find_if(modes.begin(), modes.end(), [&existingData](const auto &mode) {
0410             return existingData.mode
0411                 && mode->size() == existingData.mode->size
0412                 && mode->refreshRate() == existingData.mode->refreshRate;
0413         });
0414         const auto mode = modeIt == modes.end() ? output->currentMode() : *modeIt;
0415 
0416         const auto changeset = ret.changeSet(output);
0417         *changeset = {
0418             .mode = mode,
0419             .enabled = enable,
0420             .pos = pos,
0421             .scale = existingData.scale.value_or(chooseScale(output, mode.get())),
0422             .transform = existingData.transform.value_or(output->panelOrientation()),
0423             .manualTransform = existingData.manualTransform.value_or(output->panelOrientation()),
0424             .overscan = existingData.overscan.value_or(0),
0425             .rgbRange = existingData.rgbRange.value_or(Output::RgbRange::Automatic),
0426             .vrrPolicy = existingData.vrrPolicy.value_or(VrrPolicy::Automatic),
0427             .highDynamicRange = existingData.highDynamicRange.value_or(false),
0428             .sdrBrightness = existingData.sdrBrightness.value_or(200),
0429             .wideColorGamut = existingData.wideColorGamut.value_or(false),
0430             .autoRotationPolicy = existingData.autoRotation.value_or(Output::AutoRotationPolicy::InTabletMode),
0431         };
0432         if (enable) {
0433             const auto modeSize = changeset->transform->map(mode->size());
0434             pos.setX(std::ceil(pos.x() + modeSize.width() / *changeset->scale));
0435             outputOrder.push_back(output);
0436         }
0437     }
0438     return std::make_pair(ret, outputs);
0439 }
0440 
0441 std::shared_ptr<OutputMode> OutputConfigurationStore::chooseMode(Output *output) const
0442 {
0443     const auto modes = output->modes();
0444 
0445     // some displays advertise bigger modes than their native resolution
0446     // to avoid that, take the preferred mode into account, which is usually the native one
0447     const auto preferred = std::find_if(modes.begin(), modes.end(), [](const auto &mode) {
0448         return mode->flags() & OutputMode::Flag::Preferred;
0449     });
0450     if (preferred != modes.end()) {
0451         // some high refresh rate displays advertise a 60Hz mode as preferred for compatibility reasons
0452         // ignore that and choose the highest possible refresh rate by default instead
0453         std::shared_ptr<OutputMode> highestRefresh = *preferred;
0454         for (const auto &mode : modes) {
0455             if (mode->size() == highestRefresh->size() && mode->refreshRate() > highestRefresh->refreshRate()) {
0456                 highestRefresh = mode;
0457             }
0458         }
0459         // if the preferred mode size has a refresh rate that's too low for PCs,
0460         // allow falling back to a mode with lower resolution and a more usable refresh rate
0461         if (highestRefresh->refreshRate() >= 50000) {
0462             return highestRefresh;
0463         }
0464     }
0465 
0466     std::shared_ptr<OutputMode> ret;
0467     for (auto mode : modes) {
0468         if (mode->flags() & OutputMode::Flag::Generated) {
0469             // generated modes aren't guaranteed to work, so don't choose one as the default
0470             continue;
0471         }
0472         if (!ret) {
0473             ret = mode;
0474             continue;
0475         }
0476         const bool retUsableRefreshRate = ret->refreshRate() >= 50000;
0477         const bool usableRefreshRate = mode->refreshRate() >= 50000;
0478         if (retUsableRefreshRate && !usableRefreshRate) {
0479             ret = mode;
0480             continue;
0481         }
0482         if ((usableRefreshRate && !retUsableRefreshRate)
0483             || mode->size().width() > ret->size().width()
0484             || mode->size().height() > ret->size().height()
0485             || (mode->size() == ret->size() && mode->refreshRate() > ret->refreshRate())) {
0486             ret = mode;
0487         }
0488     }
0489     return ret;
0490 }
0491 
0492 double OutputConfigurationStore::chooseScale(Output *output, OutputMode *mode) const
0493 {
0494     if (output->physicalSize().height() <= 0) {
0495         // invalid size, can't do anything with this
0496         return 1.0;
0497     }
0498     const double outputDpi = mode->size().height() / (output->physicalSize().height() / 25.4);
0499     const double desiredScale = outputDpi / targetDpi(output);
0500     // round to 25% steps
0501     return std::clamp(std::round(100.0 * desiredScale / 25.0) * 25.0 / 100.0, 1.0, 3.0);
0502 }
0503 
0504 double OutputConfigurationStore::targetDpi(Output *output) const
0505 {
0506     // The eye's ability to perceive detail diminishes with distance, so objects
0507     // that are closer can be smaller and their details remain equally
0508     // distinguishable. As a result, each device type has its own ideal physical
0509     // size of items on its screen based on how close the user's eyes are
0510     // expected to be from it on average, and its target DPI value needs to be
0511     // changed accordingly.
0512     const auto devices = input()->devices();
0513     const bool hasLaptopLid = std::any_of(devices.begin(), devices.end(), [](const auto &device) {
0514         return device->isLidSwitch();
0515     });
0516     if (output->isInternal()) {
0517         if (hasLaptopLid) {
0518             // laptop screens: usually closer to the face than desktop monitors
0519             return 125;
0520         } else {
0521             // phone screens: even closer than laptops
0522             return 136;
0523         }
0524     } else {
0525         // "normal" 1x scale desktop monitor dpi
0526         return 96;
0527     }
0528 }
0529 
0530 void OutputConfigurationStore::load()
0531 {
0532     const QString jsonPath = QStandardPaths::locate(QStandardPaths::ConfigLocation, QStringLiteral("kwinoutputconfig.json"));
0533     if (jsonPath.isEmpty()) {
0534         return;
0535     }
0536 
0537     QFile f(jsonPath);
0538     if (!f.open(QIODevice::ReadOnly)) {
0539         qCWarning(KWIN_CORE) << "Could not open file" << jsonPath;
0540         return;
0541     }
0542     QJsonParseError error;
0543     const auto doc = QJsonDocument::fromJson(f.readAll(), &error);
0544     if (error.error != QJsonParseError::NoError) {
0545         qCWarning(KWIN_CORE) << "Failed to parse" << jsonPath << error.errorString();
0546         return;
0547     }
0548     const auto array = doc.array();
0549     std::vector<QJsonObject> objects;
0550     std::transform(array.begin(), array.end(), std::back_inserter(objects), [](const auto &json) {
0551         return json.toObject();
0552     });
0553     const auto outputsIt = std::find_if(objects.begin(), objects.end(), [](const auto &obj) {
0554         return obj["name"].toString() == "outputs" && obj["data"].isArray();
0555     });
0556     const auto setupsIt = std::find_if(objects.begin(), objects.end(), [](const auto &obj) {
0557         return obj["name"].toString() == "setups" && obj["data"].isArray();
0558     });
0559     if (outputsIt == objects.end() || setupsIt == objects.end()) {
0560         return;
0561     }
0562     const auto outputs = (*outputsIt)["data"].toArray();
0563 
0564     std::vector<std::optional<OutputState>> outputDatas;
0565     for (const auto &output : outputs) {
0566         const auto data = output.toObject();
0567         OutputState state;
0568         bool hasIdentifier = false;
0569         if (const auto it = data.find("edidIdentifier"); it != data.end()) {
0570             if (const auto str = it->toString(); !str.isEmpty()) {
0571                 state.edidIdentifier = str;
0572                 hasIdentifier = true;
0573             }
0574         }
0575         if (const auto it = data.find("edidHash"); it != data.end()) {
0576             if (const auto str = it->toString(); !str.isEmpty()) {
0577                 state.edidHash = str;
0578                 hasIdentifier = true;
0579             }
0580         }
0581         if (const auto it = data.find("connectorName"); it != data.end()) {
0582             if (const auto str = it->toString(); !str.isEmpty()) {
0583                 state.connectorName = str;
0584                 hasIdentifier = true;
0585             }
0586         }
0587         if (const auto it = data.find("mstPath"); it != data.end()) {
0588             if (const auto str = it->toString(); !str.isEmpty()) {
0589                 state.mstPath = str;
0590                 hasIdentifier = true;
0591             }
0592         }
0593         if (!hasIdentifier) {
0594             // without an identifier the settings are useless
0595             // we still have to push something into the list so that the indices stay correct
0596             outputDatas.push_back(std::nullopt);
0597             qCWarning(KWIN_CORE, "Output in config is missing identifiers");
0598             continue;
0599         }
0600         const bool hasDuplicate = std::any_of(outputDatas.begin(), outputDatas.end(), [&state](const auto &data) {
0601             return data
0602                 && data->edidIdentifier == state.edidIdentifier
0603                 && data->edidHash == state.edidHash
0604                 && data->mstPath == state.mstPath
0605                 && data->connectorName == state.connectorName;
0606         });
0607         if (hasDuplicate) {
0608             qCWarning(KWIN_CORE) << "Duplicate output found in config for edidIdentifier:" << state.edidIdentifier.value_or("<empty>") << "; connectorName:" << state.connectorName.value_or("<empty>") << "; mstPath:" << state.mstPath;
0609             outputDatas.push_back(std::nullopt);
0610             continue;
0611         }
0612         if (const auto it = data.find("mode"); it != data.end()) {
0613             const auto obj = it->toObject();
0614             const int width = obj["width"].toInt(0);
0615             const int height = obj["height"].toInt(0);
0616             const int refreshRate = obj["refreshRate"].toInt(0);
0617             if (width > 0 && height > 0 && refreshRate > 0) {
0618                 state.mode = ModeData{
0619                     .size = QSize(width, height),
0620                     .refreshRate = uint32_t(refreshRate),
0621                 };
0622             }
0623         }
0624         if (const auto it = data.find("scale"); it != data.end()) {
0625             const double scale = it->toDouble(0);
0626             if (scale > 0 && scale <= 3) {
0627                 state.scale = scale;
0628             }
0629         }
0630         if (const auto it = data.find("transform"); it != data.end()) {
0631             const auto str = it->toString();
0632             if (str == "Normal") {
0633                 state.transform = state.manualTransform = OutputTransform::Kind::Normal;
0634             } else if (str == "Rotated90") {
0635                 state.transform = state.manualTransform = OutputTransform::Kind::Rotate90;
0636             } else if (str == "Rotated180") {
0637                 state.transform = state.manualTransform = OutputTransform::Kind::Rotate180;
0638             } else if (str == "Rotated270") {
0639                 state.transform = state.manualTransform = OutputTransform::Kind::Rotate270;
0640             } else if (str == "Flipped") {
0641                 state.transform = state.manualTransform = OutputTransform::Kind::FlipX;
0642             } else if (str == "Flipped90") {
0643                 state.transform = state.manualTransform = OutputTransform::Kind::FlipX90;
0644             } else if (str == "Flipped180") {
0645                 state.transform = state.manualTransform = OutputTransform::Kind::FlipX180;
0646             } else if (str == "Flipped270") {
0647                 state.transform = state.manualTransform = OutputTransform::Kind::FlipX270;
0648             }
0649         }
0650         if (const auto it = data.find("overscan"); it != data.end()) {
0651             const int overscan = it->toInt(-1);
0652             if (overscan >= 0 && overscan <= 100) {
0653                 state.overscan = overscan;
0654             }
0655         }
0656         if (const auto it = data.find("rgbRange"); it != data.end()) {
0657             const auto str = it->toString();
0658             if (str == "Automatic") {
0659                 state.rgbRange = Output::RgbRange::Automatic;
0660             } else if (str == "Limited") {
0661                 state.rgbRange = Output::RgbRange::Limited;
0662             } else if (str == "Full") {
0663                 state.rgbRange = Output::RgbRange::Full;
0664             }
0665         }
0666         if (const auto it = data.find("vrrPolicy"); it != data.end()) {
0667             const auto str = it->toString();
0668             if (str == "Never") {
0669                 state.vrrPolicy = VrrPolicy::Never;
0670             } else if (str == "Automatic") {
0671                 state.vrrPolicy = VrrPolicy::Automatic;
0672             } else if (str == "Always") {
0673                 state.vrrPolicy = VrrPolicy::Always;
0674             }
0675         }
0676         if (const auto it = data.find("highDynamicRange"); it != data.end() && it->isBool()) {
0677             state.highDynamicRange = it->toBool();
0678         }
0679         if (const auto it = data.find("sdrBrightness"); it != data.end() && it->isDouble()) {
0680             state.sdrBrightness = it->toInt(200);
0681         }
0682         if (const auto it = data.find("wideColorGamut"); it != data.end() && it->isBool()) {
0683             state.wideColorGamut = it->toBool();
0684         }
0685         if (const auto it = data.find("autoRotation"); it != data.end()) {
0686             const auto str = it->toString();
0687             if (str == "Never") {
0688                 state.autoRotation = Output::AutoRotationPolicy::Never;
0689             } else if (str == "InTabletMode") {
0690                 state.autoRotation = Output::AutoRotationPolicy::InTabletMode;
0691             } else if (str == "Always") {
0692                 state.autoRotation = Output::AutoRotationPolicy::Always;
0693             }
0694         }
0695         if (const auto it = data.find("iccProfilePath"); it != data.end()) {
0696             state.iccProfilePath = it->toString();
0697         }
0698         if (const auto it = data.find("maxPeakBrightnessOverride"); it != data.end() && it->isDouble()) {
0699             state.maxPeakBrightnessOverride = it->toDouble();
0700         }
0701         if (const auto it = data.find("maxAverageBrightnessOverride"); it != data.end() && it->isDouble()) {
0702             state.maxAverageBrightnessOverride = it->toDouble();
0703         }
0704         if (const auto it = data.find("minBrightnessOverride"); it != data.end() && it->isDouble()) {
0705             state.minBrightnessOverride = it->toDouble();
0706         }
0707         if (const auto it = data.find("sdrGamutWideness"); it != data.end() && it->isDouble()) {
0708             state.sdrGamutWideness = it->toDouble();
0709         }
0710         outputDatas.push_back(state);
0711     }
0712 
0713     const auto setups = (*setupsIt)["data"].toArray();
0714     for (const auto &s : setups) {
0715         const auto data = s.toObject();
0716         const auto outputs = data["outputs"].toArray();
0717         Setup setup;
0718         bool fail = false;
0719         for (const auto &output : outputs) {
0720             const auto outputData = output.toObject();
0721             SetupState state;
0722             if (const auto it = outputData.find("enabled"); it != outputData.end() && it->isBool()) {
0723                 state.enabled = it->toBool();
0724             } else {
0725                 fail = true;
0726                 break;
0727             }
0728             if (const auto it = outputData.find("outputIndex"); it != outputData.end()) {
0729                 const int index = it->toInt(-1);
0730                 if (index <= -1 || size_t(index) >= outputDatas.size()) {
0731                     fail = true;
0732                     break;
0733                 }
0734                 // the outputs must be unique
0735                 const bool unique = std::none_of(setup.outputs.begin(), setup.outputs.end(), [&index](const auto &output) {
0736                     return output.outputIndex == size_t(index);
0737                 });
0738                 if (!unique) {
0739                     fail = true;
0740                     break;
0741                 }
0742                 state.outputIndex = index;
0743             }
0744             if (const auto it = outputData.find("position"); it != outputData.end()) {
0745                 const auto obj = it->toObject();
0746                 const auto x = obj.find("x");
0747                 const auto y = obj.find("y");
0748                 if (x == obj.end() || !x->isDouble() || y == obj.end() || !y->isDouble()) {
0749                     fail = true;
0750                     break;
0751                 }
0752                 state.position = QPoint(x->toInt(0), y->toInt(0));
0753             } else {
0754                 fail = true;
0755                 break;
0756             }
0757             if (const auto it = outputData.find("priority"); it != outputData.end()) {
0758                 state.priority = it->toInt(-1);
0759                 if (state.priority < 0 && state.enabled) {
0760                     fail = true;
0761                     break;
0762                 }
0763             }
0764             setup.outputs.push_back(state);
0765         }
0766         if (fail || setup.outputs.empty()) {
0767             continue;
0768         }
0769         // one of the outputs must be enabled
0770         const bool noneEnabled = std::none_of(setup.outputs.begin(), setup.outputs.end(), [](const auto &output) {
0771             return output.enabled;
0772         });
0773         if (noneEnabled) {
0774             continue;
0775         }
0776         setup.lidClosed = data["lidClosed"].toBool(false);
0777         // there must be only one setup that refers to a given set of outputs
0778         const bool alreadyExists = std::any_of(m_setups.begin(), m_setups.end(), [&setup](const auto &other) {
0779             if (setup.lidClosed != other.lidClosed || setup.outputs.size() != other.outputs.size()) {
0780                 return false;
0781             }
0782             return std::all_of(setup.outputs.begin(), setup.outputs.end(), [&other](const auto &output) {
0783                 return std::any_of(other.outputs.begin(), other.outputs.end(), [&output](const auto &otherOutput) {
0784                     return output.outputIndex == otherOutput.outputIndex;
0785                 });
0786             });
0787         });
0788         if (alreadyExists) {
0789             continue;
0790         }
0791         m_setups.push_back(setup);
0792     }
0793 
0794     // repair the outputs list in case it's broken
0795     for (size_t i = 0; i < outputDatas.size();) {
0796         if (!outputDatas[i]) {
0797             outputDatas.erase(outputDatas.begin() + i);
0798             for (auto setupIt = m_setups.begin(); setupIt != m_setups.end();) {
0799                 const bool broken = std::any_of(setupIt->outputs.begin(), setupIt->outputs.end(), [i](const auto &output) {
0800                     return output.outputIndex == i;
0801                 });
0802                 if (broken) {
0803                     setupIt = m_setups.erase(setupIt);
0804                     continue;
0805                 }
0806                 for (auto &output : setupIt->outputs) {
0807                     if (output.outputIndex > i) {
0808                         output.outputIndex--;
0809                     }
0810                 }
0811                 setupIt++;
0812             }
0813         } else {
0814             i++;
0815         }
0816     }
0817 
0818     for (const auto &o : outputDatas) {
0819         Q_ASSERT(o);
0820         m_outputs.push_back(*o);
0821     }
0822 }
0823 
0824 void OutputConfigurationStore::save()
0825 {
0826     QJsonDocument document;
0827     QJsonArray array;
0828     QJsonObject outputs;
0829     outputs["name"] = "outputs";
0830     QJsonArray outputsData;
0831     for (const auto &output : m_outputs) {
0832         QJsonObject o;
0833         if (output.edidIdentifier) {
0834             o["edidIdentifier"] = *output.edidIdentifier;
0835         }
0836         if (!output.edidHash.isEmpty()) {
0837             o["edidHash"] = output.edidHash;
0838         }
0839         if (output.connectorName) {
0840             o["connectorName"] = *output.connectorName;
0841         }
0842         if (!output.mstPath.isEmpty()) {
0843             o["mstPath"] = output.mstPath;
0844         }
0845         if (output.mode) {
0846             QJsonObject mode;
0847             mode["width"] = output.mode->size.width();
0848             mode["height"] = output.mode->size.height();
0849             mode["refreshRate"] = int(output.mode->refreshRate);
0850             o["mode"] = mode;
0851         }
0852         if (output.scale) {
0853             o["scale"] = *output.scale;
0854         }
0855         if (output.manualTransform == OutputTransform::Kind::Normal) {
0856             o["transform"] = "Normal";
0857         } else if (output.manualTransform == OutputTransform::Kind::Rotate90) {
0858             o["transform"] = "Rotated90";
0859         } else if (output.manualTransform == OutputTransform::Kind::Rotate180) {
0860             o["transform"] = "Rotated180";
0861         } else if (output.manualTransform == OutputTransform::Kind::Rotate270) {
0862             o["transform"] = "Rotated270";
0863         } else if (output.manualTransform == OutputTransform::Kind::FlipX) {
0864             o["transform"] = "Flipped";
0865         } else if (output.manualTransform == OutputTransform::Kind::FlipX90) {
0866             o["transform"] = "Flipped90";
0867         } else if (output.manualTransform == OutputTransform::Kind::FlipX180) {
0868             o["transform"] = "Flipped180";
0869         } else if (output.manualTransform == OutputTransform::Kind::FlipX270) {
0870             o["transform"] = "Flipped270";
0871         }
0872         if (output.overscan) {
0873             o["overscan"] = int(*output.overscan);
0874         }
0875         if (output.rgbRange == Output::RgbRange::Automatic) {
0876             o["rgbRange"] = "Automatic";
0877         } else if (output.rgbRange == Output::RgbRange::Limited) {
0878             o["rgbRange"] = "Limited";
0879         } else if (output.rgbRange == Output::RgbRange::Full) {
0880             o["rgbRange"] = "Full";
0881         }
0882         if (output.vrrPolicy == VrrPolicy::Never) {
0883             o["vrrPolicy"] = "Never";
0884         } else if (output.vrrPolicy == VrrPolicy::Automatic) {
0885             o["vrrPolicy"] = "Automatic";
0886         } else if (output.vrrPolicy == VrrPolicy::Always) {
0887             o["vrrPolicy"] = "Always";
0888         }
0889         if (output.highDynamicRange) {
0890             o["highDynamicRange"] = *output.highDynamicRange;
0891         }
0892         if (output.sdrBrightness) {
0893             o["sdrBrightness"] = int(*output.sdrBrightness);
0894         }
0895         if (output.wideColorGamut) {
0896             o["wideColorGamut"] = *output.wideColorGamut;
0897         }
0898         if (output.autoRotation) {
0899             switch (*output.autoRotation) {
0900             case Output::AutoRotationPolicy::Never:
0901                 o["autoRotation"] = "Never";
0902                 break;
0903             case Output::AutoRotationPolicy::InTabletMode:
0904                 o["autoRotation"] = "InTabletMode";
0905                 break;
0906             case Output::AutoRotationPolicy::Always:
0907                 o["autoRotation"] = "Always";
0908                 break;
0909             }
0910         }
0911         if (output.iccProfilePath) {
0912             o["iccProfilePath"] = *output.iccProfilePath;
0913         }
0914         if (output.maxPeakBrightnessOverride) {
0915             o["maxPeakBrightnessOverride"] = *output.maxPeakBrightnessOverride;
0916         }
0917         if (output.maxAverageBrightnessOverride) {
0918             o["maxAverageBrightnessOverride"] = *output.maxAverageBrightnessOverride;
0919         }
0920         if (output.minBrightnessOverride) {
0921             o["minBrightnessOverride"] = *output.minBrightnessOverride;
0922         }
0923         if (output.sdrGamutWideness) {
0924             o["sdrGamutWideness"] = *output.sdrGamutWideness;
0925         }
0926         outputsData.append(o);
0927     }
0928     outputs["data"] = outputsData;
0929     array.append(outputs);
0930 
0931     QJsonObject setups;
0932     setups["name"] = "setups";
0933     QJsonArray setupData;
0934     for (const auto &setup : m_setups) {
0935         QJsonObject o;
0936         o["lidClosed"] = setup.lidClosed;
0937         QJsonArray outputs;
0938         for (ssize_t i = 0; i < setup.outputs.size(); i++) {
0939             const auto &output = setup.outputs[i];
0940             QJsonObject o;
0941             o["enabled"] = output.enabled;
0942             o["outputIndex"] = int(output.outputIndex);
0943             o["priority"] = output.priority;
0944             QJsonObject pos;
0945             pos["x"] = output.position.x();
0946             pos["y"] = output.position.y();
0947             o["position"] = pos;
0948 
0949             outputs.append(o);
0950         }
0951         o["outputs"] = outputs;
0952 
0953         setupData.append(o);
0954     }
0955     setups["data"] = setupData;
0956     array.append(setups);
0957 
0958     const QString path = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/kwinoutputconfig.json";
0959     QFile f(path);
0960     if (!f.open(QIODevice::WriteOnly)) {
0961         qCWarning(KWIN_CORE, "Couldn't open output config file %s", qPrintable(path));
0962         return;
0963     }
0964     document.setArray(array);
0965     f.write(document.toJson());
0966     f.flush();
0967 }
0968 
0969 bool OutputConfigurationStore::isAutoRotateActive(const QList<Output *> &outputs, bool isTabletMode) const
0970 {
0971     const auto internalIt = std::find_if(outputs.begin(), outputs.end(), [](Output *output) {
0972         return output->isInternal() && output->isEnabled();
0973     });
0974     if (internalIt == outputs.end()) {
0975         return false;
0976     }
0977     Output *internal = *internalIt;
0978     switch (internal->autoRotationPolicy()) {
0979     case Output::AutoRotationPolicy::Never:
0980         return false;
0981     case Output::AutoRotationPolicy::InTabletMode:
0982         return isTabletMode;
0983     case Output::AutoRotationPolicy::Always:
0984         return true;
0985     }
0986     Q_UNREACHABLE();
0987 }
0988 }