From c51824b535078e9821028c164b52ff4508470753 Mon Sep 17 00:00:00 2001 From: Xaver Hugl Date: Sun, 5 Mar 2023 14:59:49 +0100 Subject: [PATCH] Add OutputManager for taking care of output settings in KWin As a first step to move away from having an external service remember output settings for KWin, this commit introduces an output manager that can load configuration files and generate new output configurations. --- src/CMakeLists.txt | 1 + src/backends/drm/drm_backend.cpp | 12 +- src/backends/drm/drm_output.cpp | 2 +- .../x11/standalone/x11_standalone_backend.cpp | 2 +- src/core/output.cpp | 5 +- src/core/output.h | 5 +- src/core/outputbackend.cpp | 10 +- src/kscreenintegration.cpp | 6 +- src/outputconfigurationstore.cpp | 132 ++++++++++++++++++ src/outputconfigurationstore.h | 33 +++++ src/utils/edid.cpp | 9 ++ src/utils/edid.h | 3 + src/wayland/outputdevice_v2_interface.cpp | 2 +- src/workspace.cpp | 22 ++- src/workspace.h | 3 +- 15 files changed, 214 insertions(+), 33 deletions(-) create mode 100644 src/outputconfigurationstore.cpp create mode 100644 src/outputconfigurationstore.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 020219a228..f008c4a4b2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -108,6 +108,7 @@ target_sources(kwin PRIVATE options.cpp osd.cpp outline.cpp + outputconfigurationstore.cpp placeholderinputeventfilter.cpp placeholderoutput.cpp placement.cpp diff --git a/src/backends/drm/drm_backend.cpp b/src/backends/drm/drm_backend.cpp index 11e450925c..c52844185f 100644 --- a/src/backends/drm/drm_backend.cpp +++ b/src/backends/drm/drm_backend.cpp @@ -473,11 +473,13 @@ bool DrmBackend::applyOutputChanges(const OutputConfiguration &config) if (output->isNonDesktop()) { continue; } - output->queueChanges(config); - if (config.constChangeSet(output)->enabled) { - toBeEnabled << output; - } else { - toBeDisabled << output; + if (const auto changeset = config.constChangeSet(output)) { + output->queueChanges(config); + if (changeset->enabled) { + toBeEnabled << output; + } else { + toBeDisabled << output; + } } } if (gpu->testPendingConfiguration() != DrmPipeline::Error::None) { diff --git a/src/backends/drm/drm_output.cpp b/src/backends/drm/drm_output.cpp index 0fb8fa3e51..fa7d41ed48 100644 --- a/src/backends/drm/drm_output.cpp +++ b/src/backends/drm/drm_output.cpp @@ -77,7 +77,7 @@ DrmOutput::DrmOutput(const std::shared_ptr &conn) .serialNumber = edid->serialNumber(), .eisaId = edid->eisaId(), .physicalSize = conn->physicalSize(), - .edid = edid->raw(), + .edid = *edid, .subPixel = conn->subpixel(), .capabilities = capabilities, .panelOrientation = DrmConnector::toKWinTransform(conn->panelOrientation()), diff --git a/src/backends/x11/standalone/x11_standalone_backend.cpp b/src/backends/x11/standalone/x11_standalone_backend.cpp index 0f56419c95..69c19373fc 100644 --- a/src/backends/x11/standalone/x11_standalone_backend.cpp +++ b/src/backends/x11/standalone/x11_standalone_backend.cpp @@ -377,7 +377,7 @@ void X11StandaloneBackend::doUpdateOutputs() information.manufacturer = edid.manufacturerString(); information.model = edid.monitorName(); information.serialNumber = edid.serialNumber(); - information.edid = data; + information.edid = edid; } } diff --git a/src/core/output.cpp b/src/core/output.cpp index 0081336302..e5c1d5edb4 100644 --- a/src/core/output.cpp +++ b/src/core/output.cpp @@ -199,7 +199,7 @@ QSize Output::pixelSize() const return orientateSize(modeSize()); } -QByteArray Output::edid() const +const Edid &Output::edid() const { return m_information.edid; } @@ -222,6 +222,9 @@ Output::SubPixel Output::subPixel() const void Output::applyChanges(const OutputConfiguration &config) { auto props = config.constChangeSet(this); + if (!props) { + return; + } Q_EMIT aboutToChange(); State next = m_state; diff --git a/src/core/output.h b/src/core/output.h index 946af1d960..7cc5e5470f 100644 --- a/src/core/output.h +++ b/src/core/output.h @@ -11,6 +11,7 @@ #include #include "renderloop.h" +#include "utils/edid.h" #include #include @@ -235,7 +236,7 @@ public: SubPixel subPixel() const; QString description() const; Capabilities capabilities() const; - QByteArray edid() const; + const Edid &edid() const; QList> modes() const; std::shared_ptr currentMode() const; DpmsMode dpmsMode() const; @@ -328,7 +329,7 @@ protected: QString serialNumber; QString eisaId; QSize physicalSize; - QByteArray edid; + Edid edid; SubPixel subPixel = SubPixel::Unknown; Capabilities capabilities; Transform panelOrientation = Transform::Normal; diff --git a/src/core/outputbackend.cpp b/src/core/outputbackend.cpp index c24ddcf93b..6e43c2b2ad 100644 --- a/src/core/outputbackend.cpp +++ b/src/core/outputbackend.cpp @@ -65,10 +65,12 @@ bool OutputBackend::applyOutputChanges(const OutputConfiguration &config) QVector toBeEnabledOutputs; QVector toBeDisabledOutputs; for (const auto &output : availableOutputs) { - if (config.constChangeSet(output)->enabled) { - toBeEnabledOutputs << output; - } else { - toBeDisabledOutputs << output; + if (const auto changeset = config.constChangeSet(output)) { + if (changeset->enabled) { + toBeEnabledOutputs << output; + } else { + toBeDisabledOutputs << output; + } } } for (const auto &output : toBeEnabledOutputs) { diff --git a/src/kscreenintegration.cpp b/src/kscreenintegration.cpp index 19584d6f3f..30df8b59b5 100644 --- a/src/kscreenintegration.cpp +++ b/src/kscreenintegration.cpp @@ -26,10 +26,8 @@ namespace KScreenIntegration /// See KScreen::Output::hashMd5 static QString outputHash(Output *output) { - if (!output->edid().isEmpty()) { - QCryptographicHash hash(QCryptographicHash::Md5); - hash.addData(output->edid()); - return QString::fromLatin1(hash.result().toHex()); + if (output->edid().isValid()) { + return output->edid().hash(); } else { return output->name(); } diff --git a/src/outputconfigurationstore.cpp b/src/outputconfigurationstore.cpp new file mode 100644 index 0000000000..df7fe11a38 --- /dev/null +++ b/src/outputconfigurationstore.cpp @@ -0,0 +1,132 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2023 Xaver Hugl + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "outputconfigurationstore.h" +#include "core/inputdevice.h" +#include "core/output.h" +#include "core/outputconfiguration.h" +#include "input.h" +#include "kscreenintegration.h" + +namespace KWin +{ + +std::pair> OutputConfigurationStore::queryConfig(const QVector &outputs) +{ + const auto kscreenConfig = KScreenIntegration::readOutputConfig(outputs, KScreenIntegration::connectedOutputsHash(outputs)); + if (kscreenConfig) { + return kscreenConfig.value(); + } else { + // no config file atm -> generate a new one + return generateConfig(outputs); + } +} + +std::pair> OutputConfigurationStore::generateConfig(const QVector &outputs) const +{ + OutputConfiguration ret; + + QPoint pos(0, 0); + for (const auto output : outputs) { + const auto mode = chooseMode(output); + const double scale = chooseScale(output, mode.get()); + *ret.changeSet(output) = { + .mode = mode, + .enabled = true, + .pos = pos, + .scale = scale, + .transform = output->panelOrientation(), + .overscan = 0, + .rgbRange = Output::RgbRange::Automatic, + .vrrPolicy = RenderLoop::VrrPolicy::Automatic, + }; + pos.setX(pos.x() + mode->size().width() / scale); + } + return std::make_pair(ret, outputs); +} + +std::shared_ptr OutputConfigurationStore::chooseMode(Output *output) const +{ + const auto modes = output->modes(); + + // some displays advertise bigger modes than their native resolution + // to avoid that, take the preferred mode into account, which is usually the native one + const auto preferred = std::find_if(modes.begin(), modes.end(), [](const auto &mode) { + return mode->flags() & OutputMode::Flag::Preferred; + }); + if (preferred != modes.end()) { + // some high refresh rate displays advertise a 60Hz mode as preferred for compatibility reasons + // ignore that and choose the highest possible refresh rate by default instead + std::shared_ptr highestRefresh = *preferred; + for (const auto &mode : modes) { + if (mode->size() == highestRefresh->size() && mode->refreshRate() > highestRefresh->refreshRate()) { + highestRefresh = mode; + } + } + // if the preferred mode size has a refresh rate that's too low for PCs, + // allow falling back to a mode with lower resolution and a more usable refresh rate + if (highestRefresh->refreshRate() >= 50000) { + return highestRefresh; + } + } + + std::shared_ptr ret; + for (auto mode : modes) { + if (!ret) { + ret = mode; + continue; + } + const bool retUsableRefreshRate = ret->refreshRate() >= 50000; + const bool usableRefreshRate = mode->refreshRate() >= 50000; + if (retUsableRefreshRate && !usableRefreshRate) { + ret = mode; + continue; + } + if ((usableRefreshRate && !retUsableRefreshRate) + || mode->size().width() > ret->size().width() + || mode->size().height() > ret->size().height() + || (mode->size() == ret->size() && mode->refreshRate() > ret->refreshRate())) { + ret = mode; + } + } + return ret; +} + +double OutputConfigurationStore::chooseScale(Output *output, OutputMode *mode) const +{ + if (output->physicalSize().height() <= 0) { + // invalid size, can't do anything with this + return 1.0; + } + const double outputDpi = mode->size().height() / (output->physicalSize().height() / 25.4); + const double desiredScale = outputDpi / targetDpi(output); + // round to 25% steps + return std::round(100.0 * desiredScale / 25.0) * 100.0 / 25.0; +} + +double OutputConfigurationStore::targetDpi(Output *output) const +{ + const auto devices = input()->devices(); + const bool hasLaptopLid = std::any_of(devices.begin(), devices.end(), [](const auto &device) { + return device->isLidSwitch(); + }); + if (output->isInternal()) { + if (hasLaptopLid) { + // laptop screens: usually closer to the face than desktop monitors + return 125; + } else { + // phone screens: even closer than laptops + return 136; + } + } else { + // "normal" 1x scale desktop monitor dpi + return 96; + } +} + +} diff --git a/src/outputconfigurationstore.h b/src/outputconfigurationstore.h new file mode 100644 index 0000000000..6014f7d513 --- /dev/null +++ b/src/outputconfigurationstore.h @@ -0,0 +1,33 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2023 Xaver Hugl + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#pragma once + +#include +#include + +namespace KWin +{ + +class OutputConfiguration; +class Output; +class OutputMode; + +class OutputConfigurationStore +{ +public: + std::pair> queryConfig(const QVector &outputs); + +private: + std::pair> generateConfig(const QVector &outputs) const; + std::shared_ptr chooseMode(Output *output) const; + double chooseScale(Output *output, OutputMode *mode) const; + double targetDpi(Output *output) const; +}; + +} diff --git a/src/utils/edid.cpp b/src/utils/edid.cpp index 149b2da18f..0f47cd05f3 100644 --- a/src/utils/edid.cpp +++ b/src/utils/edid.cpp @@ -15,6 +15,7 @@ #include #include +#include namespace KWin { @@ -175,6 +176,9 @@ Edid::Edid(const void *data, uint32_t size) m_monitorName = parseMonitorName(bytes); m_serialNumber = parseSerialNumber(bytes); m_vendor = parseVendor(bytes); + QCryptographicHash hash(QCryptographicHash::Md5); + hash.addData(m_raw); + m_hash = QString::fromLatin1(hash.result().toHex()); m_isValid = true; } @@ -241,4 +245,9 @@ QString Edid::nameString() const } } +QString Edid::hash() const +{ + return m_hash; +} + } // namespace KWin diff --git a/src/utils/edid.h b/src/utils/edid.h index 3dd89c9ecc..2733847690 100644 --- a/src/utils/edid.h +++ b/src/utils/edid.h @@ -74,12 +74,15 @@ public: */ QString nameString() const; + QString hash() const; + private: QSize m_physicalSize; QByteArray m_vendor; QByteArray m_eisaId; QByteArray m_monitorName; QByteArray m_serialNumber; + QString m_hash; QByteArray m_raw; bool m_isValid = false; diff --git a/src/wayland/outputdevice_v2_interface.cpp b/src/wayland/outputdevice_v2_interface.cpp index a2e4b53f11..f5f2e8faab 100644 --- a/src/wayland/outputdevice_v2_interface.cpp +++ b/src/wayland/outputdevice_v2_interface.cpp @@ -484,7 +484,7 @@ void OutputDeviceV2Interface::updateCurrentMode() void OutputDeviceV2Interface::updateEdid() { - d->m_edid = d->m_handle->edid(); + d->m_edid = d->m_handle->edid().raw(); const auto clientResources = d->resourceMap(); for (const auto &resource : clientResources) { d->sendEdid(resource); diff --git a/src/workspace.cpp b/src/workspace.cpp index b53d4ef658..53dde836ef 100644 --- a/src/workspace.cpp +++ b/src/workspace.cpp @@ -46,8 +46,8 @@ #include "tabbox/tabbox.h" #endif #include "decorations/decorationbridge.h" -#include "kscreenintegration.h" #include "main.h" +#include "outputconfigurationstore.h" #include "placeholderinputeventfilter.h" #include "placeholderoutput.h" #include "placementtracker.h" @@ -130,6 +130,7 @@ Workspace::Workspace() , m_focusChain(std::make_unique()) , m_applicationMenu(std::make_unique()) , m_placementTracker(std::make_unique(this)) + , m_outputConfigStore(std::make_unique()) { // If KWin was already running it saved its configuration after loosing the selection -> Reread QFuture reparseConfigFuture = QtConcurrent::run(&Options::reparseConfiguration, options); @@ -277,8 +278,8 @@ QString Workspace::getPlacementTrackerHash() QStringList hashes; for (const auto &output : std::as_const(m_outputs)) { QCryptographicHash hash(QCryptographicHash::Md5); - if (!output->edid().isEmpty()) { - hash.addData(output->edid()); + if (output->edid().isValid()) { + hash.addData(output->edid().raw()); } else { hash.addData(output->name().toLatin1()); } @@ -537,18 +538,13 @@ void Workspace::updateOutputConfiguration() setOutputOrder(newOrder); }; - m_outputsHash = KScreenIntegration::connectedOutputsHash(outputs); - if (const auto config = KScreenIntegration::readOutputConfig(outputs, m_outputsHash)) { - const auto &[cfg, order] = config.value(); - if (!kwinApp()->outputBackend()->applyOutputChanges(cfg)) { - qCWarning(KWIN_CORE) << "Applying KScreen config failed!"; - setFallbackOutputOrder(); - return; - } - setOutputOrder(order); - } else { + const auto &[cfg, order] = m_outputConfigStore->queryConfig(outputs); + if (!kwinApp()->outputBackend()->applyOutputChanges(cfg)) { + qCWarning(KWIN_CORE) << "Applying output config failed!"; setFallbackOutputOrder(); + return; } + setOutputOrder(order); } void Workspace::setupWindowConnections(Window *window) diff --git a/src/workspace.h b/src/workspace.h index 9c8cef7cf3..c3f2562c8e 100644 --- a/src/workspace.h +++ b/src/workspace.h @@ -78,6 +78,7 @@ class PlaceholderOutput; class Placement; class OutputConfiguration; class TileManager; +class OutputConfigurationStore; class KWIN_EXPORT Workspace : public QObject { @@ -632,7 +633,6 @@ private: QList m_outputs; Output *m_activeOutput = nullptr; Output *m_activeCursorOutput = nullptr; - QString m_outputsHash; QVector m_outputOrder; Window *m_activeWindow; @@ -723,6 +723,7 @@ private: PlaceholderOutput *m_placeholderOutput = nullptr; std::unique_ptr m_placeholderFilter; std::map> m_tileManagers; + std::unique_ptr m_outputConfigStore; private: friend bool performTransiencyCheck();