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.
This commit is contained in:
Xaver Hugl 2023-03-05 14:59:49 +01:00
parent 6b8e08dfa9
commit c51824b535
15 changed files with 214 additions and 33 deletions

View file

@ -108,6 +108,7 @@ target_sources(kwin PRIVATE
options.cpp options.cpp
osd.cpp osd.cpp
outline.cpp outline.cpp
outputconfigurationstore.cpp
placeholderinputeventfilter.cpp placeholderinputeventfilter.cpp
placeholderoutput.cpp placeholderoutput.cpp
placement.cpp placement.cpp

View file

@ -473,11 +473,13 @@ bool DrmBackend::applyOutputChanges(const OutputConfiguration &config)
if (output->isNonDesktop()) { if (output->isNonDesktop()) {
continue; continue;
} }
output->queueChanges(config); if (const auto changeset = config.constChangeSet(output)) {
if (config.constChangeSet(output)->enabled) { output->queueChanges(config);
toBeEnabled << output; if (changeset->enabled) {
} else { toBeEnabled << output;
toBeDisabled << output; } else {
toBeDisabled << output;
}
} }
} }
if (gpu->testPendingConfiguration() != DrmPipeline::Error::None) { if (gpu->testPendingConfiguration() != DrmPipeline::Error::None) {

View file

@ -77,7 +77,7 @@ DrmOutput::DrmOutput(const std::shared_ptr<DrmConnector> &conn)
.serialNumber = edid->serialNumber(), .serialNumber = edid->serialNumber(),
.eisaId = edid->eisaId(), .eisaId = edid->eisaId(),
.physicalSize = conn->physicalSize(), .physicalSize = conn->physicalSize(),
.edid = edid->raw(), .edid = *edid,
.subPixel = conn->subpixel(), .subPixel = conn->subpixel(),
.capabilities = capabilities, .capabilities = capabilities,
.panelOrientation = DrmConnector::toKWinTransform(conn->panelOrientation()), .panelOrientation = DrmConnector::toKWinTransform(conn->panelOrientation()),

View file

@ -377,7 +377,7 @@ void X11StandaloneBackend::doUpdateOutputs()
information.manufacturer = edid.manufacturerString(); information.manufacturer = edid.manufacturerString();
information.model = edid.monitorName(); information.model = edid.monitorName();
information.serialNumber = edid.serialNumber(); information.serialNumber = edid.serialNumber();
information.edid = data; information.edid = edid;
} }
} }

View file

@ -199,7 +199,7 @@ QSize Output::pixelSize() const
return orientateSize(modeSize()); return orientateSize(modeSize());
} }
QByteArray Output::edid() const const Edid &Output::edid() const
{ {
return m_information.edid; return m_information.edid;
} }
@ -222,6 +222,9 @@ Output::SubPixel Output::subPixel() const
void Output::applyChanges(const OutputConfiguration &config) void Output::applyChanges(const OutputConfiguration &config)
{ {
auto props = config.constChangeSet(this); auto props = config.constChangeSet(this);
if (!props) {
return;
}
Q_EMIT aboutToChange(); Q_EMIT aboutToChange();
State next = m_state; State next = m_state;

View file

@ -11,6 +11,7 @@
#include <kwin_export.h> #include <kwin_export.h>
#include "renderloop.h" #include "renderloop.h"
#include "utils/edid.h"
#include <QDebug> #include <QDebug>
#include <QMatrix3x3> #include <QMatrix3x3>
@ -235,7 +236,7 @@ public:
SubPixel subPixel() const; SubPixel subPixel() const;
QString description() const; QString description() const;
Capabilities capabilities() const; Capabilities capabilities() const;
QByteArray edid() const; const Edid &edid() const;
QList<std::shared_ptr<OutputMode>> modes() const; QList<std::shared_ptr<OutputMode>> modes() const;
std::shared_ptr<OutputMode> currentMode() const; std::shared_ptr<OutputMode> currentMode() const;
DpmsMode dpmsMode() const; DpmsMode dpmsMode() const;
@ -328,7 +329,7 @@ protected:
QString serialNumber; QString serialNumber;
QString eisaId; QString eisaId;
QSize physicalSize; QSize physicalSize;
QByteArray edid; Edid edid;
SubPixel subPixel = SubPixel::Unknown; SubPixel subPixel = SubPixel::Unknown;
Capabilities capabilities; Capabilities capabilities;
Transform panelOrientation = Transform::Normal; Transform panelOrientation = Transform::Normal;

View file

@ -65,10 +65,12 @@ bool OutputBackend::applyOutputChanges(const OutputConfiguration &config)
QVector<Output *> toBeEnabledOutputs; QVector<Output *> toBeEnabledOutputs;
QVector<Output *> toBeDisabledOutputs; QVector<Output *> toBeDisabledOutputs;
for (const auto &output : availableOutputs) { for (const auto &output : availableOutputs) {
if (config.constChangeSet(output)->enabled) { if (const auto changeset = config.constChangeSet(output)) {
toBeEnabledOutputs << output; if (changeset->enabled) {
} else { toBeEnabledOutputs << output;
toBeDisabledOutputs << output; } else {
toBeDisabledOutputs << output;
}
} }
} }
for (const auto &output : toBeEnabledOutputs) { for (const auto &output : toBeEnabledOutputs) {

View file

@ -26,10 +26,8 @@ namespace KScreenIntegration
/// See KScreen::Output::hashMd5 /// See KScreen::Output::hashMd5
static QString outputHash(Output *output) static QString outputHash(Output *output)
{ {
if (!output->edid().isEmpty()) { if (output->edid().isValid()) {
QCryptographicHash hash(QCryptographicHash::Md5); return output->edid().hash();
hash.addData(output->edid());
return QString::fromLatin1(hash.result().toHex());
} else { } else {
return output->name(); return output->name();
} }

View file

@ -0,0 +1,132 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
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<OutputConfiguration, QVector<Output *>> OutputConfigurationStore::queryConfig(const QVector<Output *> &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<OutputConfiguration, QVector<Output *>> OutputConfigurationStore::generateConfig(const QVector<Output *> &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<OutputMode> 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<OutputMode> 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<OutputMode> 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;
}
}
}

View file

@ -0,0 +1,33 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QVector>
#include <memory>
namespace KWin
{
class OutputConfiguration;
class Output;
class OutputMode;
class OutputConfigurationStore
{
public:
std::pair<OutputConfiguration, QVector<Output *>> queryConfig(const QVector<Output *> &outputs);
private:
std::pair<OutputConfiguration, QVector<Output *>> generateConfig(const QVector<Output *> &outputs) const;
std::shared_ptr<OutputMode> chooseMode(Output *output) const;
double chooseScale(Output *output, OutputMode *mode) const;
double targetDpi(Output *output) const;
};
}

View file

@ -15,6 +15,7 @@
#include <QStandardPaths> #include <QStandardPaths>
#include <KLocalizedString> #include <KLocalizedString>
#include <QCryptographicHash>
namespace KWin namespace KWin
{ {
@ -175,6 +176,9 @@ Edid::Edid(const void *data, uint32_t size)
m_monitorName = parseMonitorName(bytes); m_monitorName = parseMonitorName(bytes);
m_serialNumber = parseSerialNumber(bytes); m_serialNumber = parseSerialNumber(bytes);
m_vendor = parseVendor(bytes); m_vendor = parseVendor(bytes);
QCryptographicHash hash(QCryptographicHash::Md5);
hash.addData(m_raw);
m_hash = QString::fromLatin1(hash.result().toHex());
m_isValid = true; m_isValid = true;
} }
@ -241,4 +245,9 @@ QString Edid::nameString() const
} }
} }
QString Edid::hash() const
{
return m_hash;
}
} // namespace KWin } // namespace KWin

View file

@ -74,12 +74,15 @@ public:
*/ */
QString nameString() const; QString nameString() const;
QString hash() const;
private: private:
QSize m_physicalSize; QSize m_physicalSize;
QByteArray m_vendor; QByteArray m_vendor;
QByteArray m_eisaId; QByteArray m_eisaId;
QByteArray m_monitorName; QByteArray m_monitorName;
QByteArray m_serialNumber; QByteArray m_serialNumber;
QString m_hash;
QByteArray m_raw; QByteArray m_raw;
bool m_isValid = false; bool m_isValid = false;

View file

@ -484,7 +484,7 @@ void OutputDeviceV2Interface::updateCurrentMode()
void OutputDeviceV2Interface::updateEdid() void OutputDeviceV2Interface::updateEdid()
{ {
d->m_edid = d->m_handle->edid(); d->m_edid = d->m_handle->edid().raw();
const auto clientResources = d->resourceMap(); const auto clientResources = d->resourceMap();
for (const auto &resource : clientResources) { for (const auto &resource : clientResources) {
d->sendEdid(resource); d->sendEdid(resource);

View file

@ -46,8 +46,8 @@
#include "tabbox/tabbox.h" #include "tabbox/tabbox.h"
#endif #endif
#include "decorations/decorationbridge.h" #include "decorations/decorationbridge.h"
#include "kscreenintegration.h"
#include "main.h" #include "main.h"
#include "outputconfigurationstore.h"
#include "placeholderinputeventfilter.h" #include "placeholderinputeventfilter.h"
#include "placeholderoutput.h" #include "placeholderoutput.h"
#include "placementtracker.h" #include "placementtracker.h"
@ -130,6 +130,7 @@ Workspace::Workspace()
, m_focusChain(std::make_unique<FocusChain>()) , m_focusChain(std::make_unique<FocusChain>())
, m_applicationMenu(std::make_unique<ApplicationMenu>()) , m_applicationMenu(std::make_unique<ApplicationMenu>())
, m_placementTracker(std::make_unique<PlacementTracker>(this)) , m_placementTracker(std::make_unique<PlacementTracker>(this))
, m_outputConfigStore(std::make_unique<OutputConfigurationStore>())
{ {
// If KWin was already running it saved its configuration after loosing the selection -> Reread // If KWin was already running it saved its configuration after loosing the selection -> Reread
QFuture<void> reparseConfigFuture = QtConcurrent::run(&Options::reparseConfiguration, options); QFuture<void> reparseConfigFuture = QtConcurrent::run(&Options::reparseConfiguration, options);
@ -277,8 +278,8 @@ QString Workspace::getPlacementTrackerHash()
QStringList hashes; QStringList hashes;
for (const auto &output : std::as_const(m_outputs)) { for (const auto &output : std::as_const(m_outputs)) {
QCryptographicHash hash(QCryptographicHash::Md5); QCryptographicHash hash(QCryptographicHash::Md5);
if (!output->edid().isEmpty()) { if (output->edid().isValid()) {
hash.addData(output->edid()); hash.addData(output->edid().raw());
} else { } else {
hash.addData(output->name().toLatin1()); hash.addData(output->name().toLatin1());
} }
@ -537,18 +538,13 @@ void Workspace::updateOutputConfiguration()
setOutputOrder(newOrder); setOutputOrder(newOrder);
}; };
m_outputsHash = KScreenIntegration::connectedOutputsHash(outputs); const auto &[cfg, order] = m_outputConfigStore->queryConfig(outputs);
if (const auto config = KScreenIntegration::readOutputConfig(outputs, m_outputsHash)) { if (!kwinApp()->outputBackend()->applyOutputChanges(cfg)) {
const auto &[cfg, order] = config.value(); qCWarning(KWIN_CORE) << "Applying output config failed!";
if (!kwinApp()->outputBackend()->applyOutputChanges(cfg)) {
qCWarning(KWIN_CORE) << "Applying KScreen config failed!";
setFallbackOutputOrder();
return;
}
setOutputOrder(order);
} else {
setFallbackOutputOrder(); setFallbackOutputOrder();
return;
} }
setOutputOrder(order);
} }
void Workspace::setupWindowConnections(Window *window) void Workspace::setupWindowConnections(Window *window)

View file

@ -78,6 +78,7 @@ class PlaceholderOutput;
class Placement; class Placement;
class OutputConfiguration; class OutputConfiguration;
class TileManager; class TileManager;
class OutputConfigurationStore;
class KWIN_EXPORT Workspace : public QObject class KWIN_EXPORT Workspace : public QObject
{ {
@ -632,7 +633,6 @@ private:
QList<Output *> m_outputs; QList<Output *> m_outputs;
Output *m_activeOutput = nullptr; Output *m_activeOutput = nullptr;
Output *m_activeCursorOutput = nullptr; Output *m_activeCursorOutput = nullptr;
QString m_outputsHash;
QVector<Output *> m_outputOrder; QVector<Output *> m_outputOrder;
Window *m_activeWindow; Window *m_activeWindow;
@ -723,6 +723,7 @@ private:
PlaceholderOutput *m_placeholderOutput = nullptr; PlaceholderOutput *m_placeholderOutput = nullptr;
std::unique_ptr<PlaceholderInputEventFilter> m_placeholderFilter; std::unique_ptr<PlaceholderInputEventFilter> m_placeholderFilter;
std::map<Output *, std::unique_ptr<TileManager>> m_tileManagers; std::map<Output *, std::unique_ptr<TileManager>> m_tileManagers;
std::unique_ptr<OutputConfigurationStore> m_outputConfigStore;
private: private:
friend bool performTransiencyCheck(); friend bool performTransiencyCheck();