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