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:
parent
6b8e08dfa9
commit
c51824b535
15 changed files with 214 additions and 33 deletions
|
@ -108,6 +108,7 @@ target_sources(kwin PRIVATE
|
|||
options.cpp
|
||||
osd.cpp
|
||||
outline.cpp
|
||||
outputconfigurationstore.cpp
|
||||
placeholderinputeventfilter.cpp
|
||||
placeholderoutput.cpp
|
||||
placement.cpp
|
||||
|
|
|
@ -473,13 +473,15 @@ bool DrmBackend::applyOutputChanges(const OutputConfiguration &config)
|
|||
if (output->isNonDesktop()) {
|
||||
continue;
|
||||
}
|
||||
if (const auto changeset = config.constChangeSet(output)) {
|
||||
output->queueChanges(config);
|
||||
if (config.constChangeSet(output)->enabled) {
|
||||
if (changeset->enabled) {
|
||||
toBeEnabled << output;
|
||||
} else {
|
||||
toBeDisabled << output;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (gpu->testPendingConfiguration() != DrmPipeline::Error::None) {
|
||||
for (const auto &output : std::as_const(toBeEnabled)) {
|
||||
output->revertQueuedChanges();
|
||||
|
|
|
@ -77,7 +77,7 @@ DrmOutput::DrmOutput(const std::shared_ptr<DrmConnector> &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()),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <kwin_export.h>
|
||||
|
||||
#include "renderloop.h"
|
||||
#include "utils/edid.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QMatrix3x3>
|
||||
|
@ -235,7 +236,7 @@ public:
|
|||
SubPixel subPixel() const;
|
||||
QString description() const;
|
||||
Capabilities capabilities() const;
|
||||
QByteArray edid() const;
|
||||
const Edid &edid() const;
|
||||
QList<std::shared_ptr<OutputMode>> modes() const;
|
||||
std::shared_ptr<OutputMode> 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;
|
||||
|
|
|
@ -65,12 +65,14 @@ bool OutputBackend::applyOutputChanges(const OutputConfiguration &config)
|
|||
QVector<Output *> toBeEnabledOutputs;
|
||||
QVector<Output *> toBeDisabledOutputs;
|
||||
for (const auto &output : availableOutputs) {
|
||||
if (config.constChangeSet(output)->enabled) {
|
||||
if (const auto changeset = config.constChangeSet(output)) {
|
||||
if (changeset->enabled) {
|
||||
toBeEnabledOutputs << output;
|
||||
} else {
|
||||
toBeDisabledOutputs << output;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto &output : toBeEnabledOutputs) {
|
||||
output->applyChanges(config);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
132
src/outputconfigurationstore.cpp
Normal file
132
src/outputconfigurationstore.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
33
src/outputconfigurationstore.h
Normal file
33
src/outputconfigurationstore.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
|
@ -15,6 +15,7 @@
|
|||
#include <QStandardPaths>
|
||||
|
||||
#include <KLocalizedString>
|
||||
#include <QCryptographicHash>
|
||||
|
||||
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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<FocusChain>())
|
||||
, m_applicationMenu(std::make_unique<ApplicationMenu>())
|
||||
, 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
|
||||
QFuture<void> 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();
|
||||
const auto &[cfg, order] = m_outputConfigStore->queryConfig(outputs);
|
||||
if (!kwinApp()->outputBackend()->applyOutputChanges(cfg)) {
|
||||
qCWarning(KWIN_CORE) << "Applying KScreen config failed!";
|
||||
qCWarning(KWIN_CORE) << "Applying output config failed!";
|
||||
setFallbackOutputOrder();
|
||||
return;
|
||||
}
|
||||
setOutputOrder(order);
|
||||
} else {
|
||||
setFallbackOutputOrder();
|
||||
}
|
||||
}
|
||||
|
||||
void Workspace::setupWindowConnections(Window *window)
|
||||
|
|
|
@ -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<Output *> m_outputs;
|
||||
Output *m_activeOutput = nullptr;
|
||||
Output *m_activeCursorOutput = nullptr;
|
||||
QString m_outputsHash;
|
||||
QVector<Output *> m_outputOrder;
|
||||
|
||||
Window *m_activeWindow;
|
||||
|
@ -723,6 +723,7 @@ private:
|
|||
PlaceholderOutput *m_placeholderOutput = nullptr;
|
||||
std::unique_ptr<PlaceholderInputEventFilter> m_placeholderFilter;
|
||||
std::map<Output *, std::unique_ptr<TileManager>> m_tileManagers;
|
||||
std::unique_ptr<OutputConfigurationStore> m_outputConfigStore;
|
||||
|
||||
private:
|
||||
friend bool performTransiencyCheck();
|
||||
|
|
Loading…
Reference in a new issue