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:
parent
66377359e1
commit
386814176b
2 changed files with 100 additions and 96 deletions
|
@ -31,12 +31,14 @@
|
||||||
// KWayland
|
// KWayland
|
||||||
#include <KWaylandServer/seat_interface.h>
|
#include <KWaylandServer/seat_interface.h>
|
||||||
// KF5
|
// KF5
|
||||||
#include <KConfigGroup>
|
|
||||||
#include <KCoreAddons>
|
#include <KCoreAddons>
|
||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
#include <KSharedConfig>
|
|
||||||
// Qt
|
// Qt
|
||||||
#include <QCryptographicHash>
|
#include <QCryptographicHash>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
#include <QSocketNotifier>
|
#include <QSocketNotifier>
|
||||||
// system
|
// system
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
@ -67,7 +69,6 @@ DrmBackend::DrmBackend(QObject *parent)
|
||||||
|
|
||||||
DrmBackend::~DrmBackend()
|
DrmBackend::~DrmBackend()
|
||||||
{
|
{
|
||||||
writeOutputsConfiguration();
|
|
||||||
qDeleteAll(m_gpus);
|
qDeleteAll(m_gpus);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -345,43 +346,87 @@ bool DrmBackend::updateOutputs()
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static QString transformToString(DrmOutput::Transform transform)
|
namespace KWinKScreenIntegration
|
||||||
{
|
{
|
||||||
switch (transform) {
|
/// See KScreen::Output::hashMd5
|
||||||
case DrmOutput::Transform::Normal:
|
QString outputHash(DrmOutput *output)
|
||||||
return QStringLiteral("normal");
|
{
|
||||||
case DrmOutput::Transform::Rotated90:
|
QCryptographicHash hash(QCryptographicHash::Md5);
|
||||||
return QStringLiteral("rotate-90");
|
if (!output->edid().isEmpty()) {
|
||||||
case DrmOutput::Transform::Rotated180:
|
hash.addData(output->edid());
|
||||||
return QStringLiteral("rotate-180");
|
} else {
|
||||||
case DrmOutput::Transform::Rotated270:
|
hash.addData(output->name().toLatin1());
|
||||||
return QStringLiteral("rotate-270");
|
}
|
||||||
case DrmOutput::Transform::Flipped:
|
return QString::fromLatin1(hash.result().toHex());
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static DrmOutput::Transform stringToTransform(const QString &text)
|
/// See KScreen::Config::connectedOutputsHash in libkscreen
|
||||||
{
|
QString connectedOutputsHash(const QVector<DrmOutput*> &outputs)
|
||||||
static const QHash<QString, DrmOutput::Transform> stringToTransform {
|
{
|
||||||
{ QStringLiteral("normal"), DrmOutput::Transform::Normal },
|
QStringList hashedOutputs;
|
||||||
{ QStringLiteral("rotate-90"), DrmOutput::Transform::Rotated90 },
|
hashedOutputs.reserve(outputs.count());
|
||||||
{ QStringLiteral("rotate-180"), DrmOutput::Transform::Rotated180 },
|
for (auto output : qAsConst(outputs)) {
|
||||||
{ QStringLiteral("rotate-270"), DrmOutput::Transform::Rotated270 },
|
hashedOutputs << outputHash(output);
|
||||||
{ QStringLiteral("flip"), DrmOutput::Transform::Flipped },
|
}
|
||||||
{ QStringLiteral("flip-90"), DrmOutput::Transform::Flipped90 },
|
std::sort(hashedOutputs.begin(), hashedOutputs.end());
|
||||||
{ QStringLiteral("flip-180"), DrmOutput::Transform::Flipped180 },
|
const auto hash = QCryptographicHash::hash(hashedOutputs.join(QString()).toLatin1(), QCryptographicHash::Md5);
|
||||||
{ QStringLiteral("flip-270"), DrmOutput::Transform::Flipped270 }
|
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()
|
void DrmBackend::readOutputsConfiguration()
|
||||||
|
@ -389,72 +434,33 @@ void DrmBackend::readOutputsConfiguration()
|
||||||
if (m_outputs.isEmpty()) {
|
if (m_outputs.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const QString uuid = generateOutputConfigurationUuid();
|
const auto outputsInfo = KWinKScreenIntegration::outputsConfig(m_outputs);
|
||||||
const auto outputGroup = kwinApp()->config()->group("DrmOutputs");
|
|
||||||
const auto configGroup = outputGroup.group(uuid);
|
|
||||||
// default position goes from left to right
|
// default position goes from left to right
|
||||||
QPoint pos(0, 0);
|
QPoint pos(0, 0);
|
||||||
for (auto it = m_outputs.begin(); it != m_outputs.end(); ++it) {
|
for (auto it = m_outputs.begin(); it != m_outputs.end(); ++it) {
|
||||||
qCDebug(KWIN_DRM) << "Reading output configuration for [" << uuid << "] ["<< (*it)->uuid() << "]";
|
const QJsonObject outputInfo = outputsInfo[*it];
|
||||||
const auto outputConfig = configGroup.group((*it)->uuid().toString(QUuid::WithoutBraces));
|
qCDebug(KWIN_DRM) << "Reading output configuration for " << *it;
|
||||||
(*it)->setGlobalPos(outputConfig.readEntry<QPoint>("Position", pos));
|
if (!outputInfo.isEmpty()) {
|
||||||
if (outputConfig.hasKey("Scale"))
|
const QJsonObject pos = outputInfo["pos"].toObject();
|
||||||
(*it)->setScale(outputConfig.readEntry("Scale", 1.0));
|
(*it)->setGlobalPos({pos["x"].toInt(), pos["y"].toInt()});
|
||||||
(*it)->setTransformInternal(stringToTransform(outputConfig.readEntry("Transform", "normal")));
|
if (const QJsonValue scale = outputInfo["scale"]; !scale.isUndefined()) {
|
||||||
pos.setX(pos.x() + (*it)->geometry().width());
|
(*it)->setScale(scale.toDouble(1.));
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
(*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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
void DrmBackend::enableOutput(DrmOutput *output, bool enable)
|
||||||
{
|
{
|
||||||
if (enable) {
|
if (enable) {
|
||||||
|
|
|
@ -89,8 +89,6 @@ private:
|
||||||
void moveCursor();
|
void moveCursor();
|
||||||
void initCursor();
|
void initCursor();
|
||||||
void readOutputsConfiguration();
|
void readOutputsConfiguration();
|
||||||
void writeOutputsConfiguration();
|
|
||||||
QString generateOutputConfigurationUuid() const;
|
|
||||||
void handleUdevEvent();
|
void handleUdevEvent();
|
||||||
DrmGpu *addGpu(const QString &fileName);
|
DrmGpu *addGpu(const QString &fileName);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue