drm: Use KScreen's json files to set up the initial output composition

So far we had a composition setup within kwinrc and kscreen. This
produces flickering sometime and makes the state a bit more flimsy.
This patch changes the kwin's behaviour to use the files produced by
kscreen which are anyways available down the line.

THis simplifies our behaviour down to just one format that we write to
and feed from. This also allows us to leverage it further by using this
format for default setups (which consist in the right file in
~/.local/share/kscreen).
This commit is contained in:
Aleix Pol 2021-05-06 02:13:19 +02:00 committed by Aleix Pol Gonzalez
parent 66377359e1
commit 386814176b
2 changed files with 100 additions and 96 deletions

View file

@ -31,12 +31,14 @@
// KWayland
#include <KWaylandServer/seat_interface.h>
// KF5
#include <KConfigGroup>
#include <KCoreAddons>
#include <KLocalizedString>
#include <KSharedConfig>
// Qt
#include <QCryptographicHash>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QSocketNotifier>
// system
#include <algorithm>
@ -67,7 +69,6 @@ DrmBackend::DrmBackend(QObject *parent)
DrmBackend::~DrmBackend()
{
writeOutputsConfiguration();
qDeleteAll(m_gpus);
}
@ -345,43 +346,87 @@ bool DrmBackend::updateOutputs()
return true;
}
static QString transformToString(DrmOutput::Transform transform)
namespace KWinKScreenIntegration
{
switch (transform) {
case DrmOutput::Transform::Normal:
return QStringLiteral("normal");
case DrmOutput::Transform::Rotated90:
return QStringLiteral("rotate-90");
case DrmOutput::Transform::Rotated180:
return QStringLiteral("rotate-180");
case DrmOutput::Transform::Rotated270:
return QStringLiteral("rotate-270");
case DrmOutput::Transform::Flipped:
return QStringLiteral("flip");
case DrmOutput::Transform::Flipped90:
return QStringLiteral("flip-90");
case DrmOutput::Transform::Flipped180:
return QStringLiteral("flip-180");
case DrmOutput::Transform::Flipped270:
return QStringLiteral("flip-270");
default:
return QStringLiteral("normal");
/// See KScreen::Output::hashMd5
QString outputHash(DrmOutput *output)
{
QCryptographicHash hash(QCryptographicHash::Md5);
if (!output->edid().isEmpty()) {
hash.addData(output->edid());
} else {
hash.addData(output->name().toLatin1());
}
return QString::fromLatin1(hash.result().toHex());
}
}
static DrmOutput::Transform stringToTransform(const QString &text)
{
static const QHash<QString, DrmOutput::Transform> stringToTransform {
{ QStringLiteral("normal"), DrmOutput::Transform::Normal },
{ QStringLiteral("rotate-90"), DrmOutput::Transform::Rotated90 },
{ QStringLiteral("rotate-180"), DrmOutput::Transform::Rotated180 },
{ QStringLiteral("rotate-270"), DrmOutput::Transform::Rotated270 },
{ QStringLiteral("flip"), DrmOutput::Transform::Flipped },
{ QStringLiteral("flip-90"), DrmOutput::Transform::Flipped90 },
{ QStringLiteral("flip-180"), DrmOutput::Transform::Flipped180 },
{ QStringLiteral("flip-270"), DrmOutput::Transform::Flipped270 }
/// See KScreen::Config::connectedOutputsHash in libkscreen
QString connectedOutputsHash(const QVector<DrmOutput*> &outputs)
{
QStringList hashedOutputs;
hashedOutputs.reserve(outputs.count());
for (auto output : qAsConst(outputs)) {
hashedOutputs << outputHash(output);
}
std::sort(hashedOutputs.begin(), hashedOutputs.end());
const auto hash = QCryptographicHash::hash(hashedOutputs.join(QString()).toLatin1(), QCryptographicHash::Md5);
return QString::fromLatin1(hash.toHex());
}
QMap<DrmOutput*, QJsonObject> outputsConfig(const QVector<DrmOutput*> &outputs)
{
const QString kscreenJsonPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kscreen/") % connectedOutputsHash(outputs));
QFile f(kscreenJsonPath);
if (!f.open(QIODevice::ReadOnly)) {
qCWarning(KWIN_DRM) << "Could not open file" << kscreenJsonPath;
return {};
}
QJsonParseError error;
const auto doc = QJsonDocument::fromJson(f.readAll(), &error);
if (error.error != QJsonParseError::NoError) {
qCWarning(KWIN_DRM) << "Failed to parse" << kscreenJsonPath << error.errorString();
return {};
}
QMap<DrmOutput*, QJsonObject> ret;
const auto outputsJson = doc.array();
for (const auto &outputJson : outputsJson) {
const auto outputObject = outputJson.toObject();
for (auto it = outputs.constBegin(), itEnd = outputs.constEnd(); it != itEnd; ) {
if (!ret.contains(*it) && outputObject["id"] == outputHash(*it)) {
ret[*it] = outputObject;
continue;
}
++it;
}
}
return ret;
}
/// See KScreen::Output::Rotation
enum Rotation {
None = 1,
Left = 2,
Inverted = 4,
Right = 8,
};
return stringToTransform.value(text, DrmOutput::Transform::Normal);
DrmOutput::Transform toDrmTransform(int rotation)
{
switch (Rotation(rotation)) {
case None:
return DrmOutput::Transform::Normal;
case Left:
return DrmOutput::Transform::Rotated90;
case Inverted:
return DrmOutput::Transform::Rotated180;
case Right:
return DrmOutput::Transform::Rotated270;
default:
Q_UNREACHABLE();
}
}
}
void DrmBackend::readOutputsConfiguration()
@ -389,70 +434,31 @@ void DrmBackend::readOutputsConfiguration()
if (m_outputs.isEmpty()) {
return;
}
const QString uuid = generateOutputConfigurationUuid();
const auto outputGroup = kwinApp()->config()->group("DrmOutputs");
const auto configGroup = outputGroup.group(uuid);
const auto outputsInfo = KWinKScreenIntegration::outputsConfig(m_outputs);
// default position goes from left to right
QPoint pos(0, 0);
for (auto it = m_outputs.begin(); it != m_outputs.end(); ++it) {
qCDebug(KWIN_DRM) << "Reading output configuration for [" << uuid << "] ["<< (*it)->uuid() << "]";
const auto outputConfig = configGroup.group((*it)->uuid().toString(QUuid::WithoutBraces));
(*it)->setGlobalPos(outputConfig.readEntry<QPoint>("Position", pos));
if (outputConfig.hasKey("Scale"))
(*it)->setScale(outputConfig.readEntry("Scale", 1.0));
(*it)->setTransformInternal(stringToTransform(outputConfig.readEntry("Transform", "normal")));
const QJsonObject outputInfo = outputsInfo[*it];
qCDebug(KWIN_DRM) << "Reading output configuration for " << *it;
if (!outputInfo.isEmpty()) {
const QJsonObject pos = outputInfo["pos"].toObject();
(*it)->setGlobalPos({pos["x"].toInt(), pos["y"].toInt()});
if (const QJsonValue scale = outputInfo["scale"]; !scale.isUndefined()) {
(*it)->setScale(scale.toDouble(1.));
}
(*it)->setTransformInternal(KWinKScreenIntegration::toDrmTransform(outputInfo["rotation"].toInt()));
if (const QJsonObject mode = outputInfo["mode"].toObject(); !mode.isEmpty()) {
const QJsonObject size = mode["size"].toObject();
(*it)->updateMode(size["width"].toInt(), size["height"].toInt(), mode["refresh"].toDouble() * 1000);
}
} else {
(*it)->setGlobalPos(pos);
(*it)->setTransformInternal(DrmOutput::Transform::Normal);
}
pos.setX(pos.x() + (*it)->geometry().width());
if (outputConfig.hasKey("Mode")) {
QString mode = outputConfig.readEntry("Mode");
QStringList list = mode.split("_");
if (list.size() > 1) {
QStringList size = list[0].split("x");
if (size.size() > 1) {
int width = size[0].toInt();
int height = size[1].toInt();
int refreshRate = list[1].toInt();
(*it)->updateMode(width, height, refreshRate);
}
}
}
}
}
void DrmBackend::writeOutputsConfiguration()
{
if (m_outputs.isEmpty()) {
return;
}
const QString uuid = generateOutputConfigurationUuid();
auto configGroup = KSharedConfig::openConfig()->group("DrmOutputs").group(uuid);
// default position goes from left to right
for (auto it = m_outputs.cbegin(); it != m_outputs.cend(); ++it) {
qCDebug(KWIN_DRM) << "Writing output configuration for [" << uuid << "] ["<< (*it)->uuid() << "]";
auto outputConfig = configGroup.group((*it)->uuid().toString(QUuid::WithoutBraces));
outputConfig.writeEntry("Scale", (*it)->scale());
outputConfig.writeEntry("Transform", transformToString((*it)->transform()));
QString mode;
mode += QString::number((*it)->modeSize().width());
mode += "x";
mode += QString::number((*it)->modeSize().height());
mode += "_";
mode += QString::number((*it)->refreshRate());
outputConfig.writeEntry("Mode", mode);
}
}
QString DrmBackend::generateOutputConfigurationUuid() const
{
auto it = m_outputs.constBegin();
if (m_outputs.size() == 1) {
// special case: one output
return (*it)->uuid().toString(QUuid::WithoutBraces);
}
QCryptographicHash hash(QCryptographicHash::Md5);
for (const DrmOutput *output: qAsConst(m_outputs)) {
hash.addData(output->uuid().toByteArray());
}
return QString::fromLocal8Bit(hash.result().toHex().left(10));
}
void DrmBackend::enableOutput(DrmOutput *output, bool enable)

View file

@ -89,8 +89,6 @@ private:
void moveCursor();
void initCursor();
void readOutputsConfiguration();
void writeOutputsConfiguration();
QString generateOutputConfigurationUuid() const;
void handleUdevEvent();
DrmGpu *addGpu(const QString &fileName);