From 8c257840700e55ad3dfb2521ad572eeae4096d9b Mon Sep 17 00:00:00 2001 From: Xaver Hugl Date: Wed, 22 May 2024 21:19:20 +0200 Subject: [PATCH] outputconfigurationstore: split user configured and automatically set modes If the mode the user has previously set through KScreen isn't available, this falls back to a different mode without overwriting the user preference. If on the next hotplug, the desired mode is available again, it then gets chosen instead of the fallback BUG: 484037 --- src/backends/drm/drm_output.cpp | 2 ++ src/core/output.cpp | 12 +++++++++ src/core/output.h | 4 +++ src/core/outputconfiguration.h | 2 ++ src/outputconfigurationstore.cpp | 39 +++++++++++++++++++++-------- src/wayland/outputmanagement_v2.cpp | 10 +++++++- 6 files changed, 58 insertions(+), 11 deletions(-) diff --git a/src/backends/drm/drm_output.cpp b/src/backends/drm/drm_output.cpp index 6905da5d63..68d43d7ae4 100644 --- a/src/backends/drm/drm_output.cpp +++ b/src/backends/drm/drm_output.cpp @@ -405,6 +405,8 @@ void DrmOutput::applyQueuedChanges(const std::shared_ptr &props next.vrrPolicy = props->vrrPolicy.value_or(m_state.vrrPolicy); next.colorProfileSource = props->colorProfileSource.value_or(m_state.colorProfileSource); next.brightness = props->brightness.value_or(m_state.brightness); + next.desiredModeSize = props->desiredModeSize.value_or(m_state.desiredModeSize); + next.desiredModeRefreshRate = props->desiredModeRefreshRate.value_or(m_state.desiredModeRefreshRate); setState(next); if (!isEnabled() && m_pipeline->needsModeset()) { diff --git a/src/core/output.cpp b/src/core/output.cpp index e341c67d74..18404ccc5a 100644 --- a/src/core/output.cpp +++ b/src/core/output.cpp @@ -487,6 +487,16 @@ std::shared_ptr Output::currentMode() const return m_state.currentMode; } +QSize Output::desiredModeSize() const +{ + return m_state.desiredModeSize; +} + +uint32_t Output::desiredModeRefreshRate() const +{ + return m_state.desiredModeRefreshRate; +} + Output::SubPixel Output::subPixel() const { return m_information.subPixel; @@ -512,6 +522,8 @@ void Output::applyChanges(const OutputConfiguration &config) next.iccProfile = IccProfile::load(*props->iccProfilePath); } next.vrrPolicy = props->vrrPolicy.value_or(m_state.vrrPolicy); + next.desiredModeSize = props->desiredModeSize.value_or(m_state.desiredModeSize); + next.desiredModeRefreshRate = props->desiredModeRefreshRate.value_or(m_state.desiredModeRefreshRate); setState(next); diff --git a/src/core/output.h b/src/core/output.h index e3d52bd62c..7a7938ba63 100644 --- a/src/core/output.h +++ b/src/core/output.h @@ -321,6 +321,8 @@ public: const Edid &edid() const; QList> modes() const; std::shared_ptr currentMode() const; + QSize desiredModeSize() const; + uint32_t desiredModeRefreshRate() const; DpmsMode dpmsMode() const; virtual void setDpmsMode(DpmsMode mode); @@ -457,6 +459,8 @@ protected: OutputTransform manualTransform = OutputTransform::Normal; QList> modes; std::shared_ptr currentMode; + QSize desiredModeSize; + uint32_t desiredModeRefreshRate = 0; DpmsMode dpmsMode = DpmsMode::On; SubPixel subPixel = SubPixel::Unknown; bool enabled = false; diff --git a/src/core/outputconfiguration.h b/src/core/outputconfiguration.h index 512b181a49..746b2c4a00 100644 --- a/src/core/outputconfiguration.h +++ b/src/core/outputconfiguration.h @@ -24,6 +24,8 @@ class KWIN_EXPORT OutputChangeSet { public: std::optional> mode; + std::optional desiredModeSize; + std::optional desiredModeRefreshRate; std::optional enabled; std::optional pos; std::optional scale; diff --git a/src/outputconfigurationstore.cpp b/src/outputconfigurationstore.cpp index 47bbb5a72c..8b5c2753f5 100644 --- a/src/outputconfigurationstore.cpp +++ b/src/outputconfigurationstore.cpp @@ -200,9 +200,13 @@ void OutputConfigurationStore::storeConfig(const QList &allOutputs, bo outputIt = setup->outputs.end() - 1; } if (const auto changeSet = config.constChangeSet(output)) { - std::shared_ptr mode = changeSet->mode.value_or(output->currentMode()).lock(); - if (!mode) { - mode = output->currentMode(); + QSize modeSize = changeSet->desiredModeSize.value_or(output->desiredModeSize()); + if (modeSize.isEmpty()) { + modeSize = output->currentMode()->size(); + } + uint32_t refreshRate = changeSet->desiredModeRefreshRate.value_or(output->desiredModeRefreshRate()); + if (refreshRate == 0) { + refreshRate = output->currentMode()->refreshRate(); } m_outputs[*outputIndex] = OutputState{ .edidIdentifier = output->edid().identifier(), @@ -210,8 +214,8 @@ void OutputConfigurationStore::storeConfig(const QList &allOutputs, bo .edidHash = output->edid().isValid() ? output->edid().hash() : QString{}, .mstPath = output->mstPath(), .mode = ModeData{ - .size = mode->size(), - .refreshRate = mode->refreshRate(), + .size = modeSize, + .refreshRate = refreshRate, }, .scale = changeSet->scale.value_or(output->scale()), .transform = changeSet->transform.value_or(output->transform()), @@ -238,15 +242,22 @@ void OutputConfigurationStore::storeConfig(const QList &allOutputs, bo .priority = int(outputOrder.indexOf(output)), }; } else { - const auto mode = output->currentMode(); + QSize modeSize = output->desiredModeSize(); + if (modeSize.isEmpty()) { + modeSize = output->currentMode()->size(); + } + uint32_t refreshRate = output->desiredModeRefreshRate(); + if (refreshRate == 0) { + refreshRate = output->currentMode()->refreshRate(); + } m_outputs[*outputIndex] = OutputState{ .edidIdentifier = output->edid().identifier(), .connectorName = output->name(), .edidHash = output->edid().isValid() ? output->edid().hash() : QString{}, .mstPath = output->mstPath(), .mode = ModeData{ - .size = mode->size(), - .refreshRate = mode->refreshRate(), + .size = modeSize, + .refreshRate = refreshRate, }, .scale = output->scale(), .transform = output->transform(), @@ -287,13 +298,19 @@ std::pair> OutputConfigurationStore::setupT return state.outputIndex == outputIndex; }); const auto modes = output->modes(); - const auto mode = std::find_if(modes.begin(), modes.end(), [&state](const auto &mode) { + const auto modeIt = std::find_if(modes.begin(), modes.end(), [&state](const auto &mode) { return state.mode && mode->size() == state.mode->size && mode->refreshRate() == state.mode->refreshRate; }); + std::optional> mode = modeIt == modes.end() ? std::nullopt : std::optional(*modeIt); + if (!mode.has_value()) { + mode = chooseMode(output); + } *ret.changeSet(output) = OutputChangeSet{ - .mode = mode == modes.end() ? std::nullopt : std::optional(*mode), + .mode = mode, + .desiredModeSize = state.mode.has_value() ? std::make_optional(state.mode->size) : std::nullopt, + .desiredModeRefreshRate = state.mode.has_value() ? std::make_optional(state.mode->refreshRate) : std::nullopt, .enabled = setupState.enabled, .pos = setupState.position, .scale = state.scale, @@ -420,6 +437,8 @@ std::pair> OutputConfigurationStore::genera const auto changeset = ret.changeSet(output); *changeset = { .mode = mode, + .desiredModeSize = mode->size(), + .desiredModeRefreshRate = mode->refreshRate(), .enabled = kscreenChangeSet.enabled.value_or(enable), .pos = pos, // kscreen scale is unreliable because it gets overwritten with the value 1 on Xorg, diff --git a/src/wayland/outputmanagement_v2.cpp b/src/wayland/outputmanagement_v2.cpp index c2020f28f8..4f84781102 100644 --- a/src/wayland/outputmanagement_v2.cpp +++ b/src/wayland/outputmanagement_v2.cpp @@ -121,7 +121,15 @@ void OutputConfigurationV2Interface::kde_output_configuration_v2_mode(Resource * OutputDeviceV2Interface *output = OutputDeviceV2Interface::get(outputdevice); OutputDeviceModeV2Interface *mode = OutputDeviceModeV2Interface::get(modeResource); if (output && mode) { - config.changeSet(output->handle())->mode = mode->handle().lock(); + const auto change = config.changeSet(output->handle()); + const auto modePtr = mode->handle().lock(); + if (!modePtr) { + invalid = true; + return; + } + change->mode = modePtr; + change->desiredModeSize = modePtr->size(); + change->desiredModeRefreshRate = modePtr->refreshRate(); } else { invalid = true; }