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()