From b5a4c08231c7b5521bbe184676c34bd2c7071725 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Wed, 17 Aug 2022 15:39:49 +0300 Subject: [PATCH] Move kscreen integration in Workspace The main motivation behind moving kscreen integration to the Workspace is to make output configuration work the same way regardless of the backend and simplify the drm backend. --- src/backends/drm/drm_backend.cpp | 176 ------------------------------ src/backends/drm/drm_backend.h | 1 - src/workspace.cpp | 181 +++++++++++++++++++++++++++++++ src/workspace.h | 4 + 4 files changed, 185 insertions(+), 177 deletions(-) diff --git a/src/backends/drm/drm_backend.cpp b/src/backends/drm/drm_backend.cpp index 281e1d59cd..4cc0bbab4a 100644 --- a/src/backends/drm/drm_backend.cpp +++ b/src/backends/drm/drm_backend.cpp @@ -32,16 +32,10 @@ #include #include // Qt -#include -#include -#include -#include -#include #include // system #include #include -#include #include #include // drm @@ -334,7 +328,6 @@ void DrmBackend::removeOutput(DrmAbstractOutput *o) void DrmBackend::updateOutputs() { - const auto oldOutputs = m_outputs; for (auto it = m_gpus.begin(); it < m_gpus.end();) { auto gpu = it->get(); gpu->updateOutputs(); @@ -357,177 +350,9 @@ void DrmBackend::updateOutputs() return false; } }); - if (oldOutputs != m_outputs) { - readOutputsConfiguration(m_outputs); - } Q_EMIT screensQueried(); } -namespace KWinKScreenIntegration -{ -/// See KScreen::Output::hashMd5 -QString outputHash(DrmAbstractOutput *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()); -} - -/// See KScreen::Config::connectedOutputsHash in libkscreen -QString connectedOutputsHash(const QVector &outputs) -{ - QStringList hashedOutputs; - hashedOutputs.reserve(outputs.count()); - for (auto output : qAsConst(outputs)) { - if (!output->isPlaceholder() && !output->isNonDesktop()) { - 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 outputsConfig(const QVector &outputs) -{ - const QString kscreenJsonPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kscreen/") % connectedOutputsHash(outputs)); - if (kscreenJsonPath.isEmpty()) { - return {}; - } - - 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 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, -}; - -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(); - } -} - -std::shared_ptr parseMode(Output *output, const QJsonObject &modeInfo) -{ - const QJsonObject size = modeInfo["size"].toObject(); - const QSize modeSize = QSize(size["width"].toInt(), size["height"].toInt()); - const int refreshRate = round(modeInfo["refresh"].toDouble() * 1000); - - const auto modes = output->modes(); - auto it = std::find_if(modes.begin(), modes.end(), [&modeSize, &refreshRate](const auto &mode) { - return mode->size() == modeSize && mode->refreshRate() == refreshRate; - }); - return (it != modes.end()) ? *it : nullptr; -} -} - -bool DrmBackend::readOutputsConfiguration(const QVector &outputs) -{ - Q_ASSERT(!outputs.isEmpty()); - const auto outputsInfo = KWinKScreenIntegration::outputsConfig(outputs); - - Output *primaryOutput = outputs.constFirst(); - OutputConfiguration cfg; - // default position goes from left to right - QPoint pos(0, 0); - for (const auto &output : qAsConst(outputs)) { - if (output->isPlaceholder() || output->isNonDesktop()) { - continue; - } - auto props = cfg.changeSet(output); - const QJsonObject outputInfo = outputsInfo[output]; - qCDebug(KWIN_DRM) << "Reading output configuration for " << output; - if (!outputInfo.isEmpty()) { - if (outputInfo["primary"].toBool()) { - primaryOutput = output; - } - props->enabled = outputInfo["enabled"].toBool(true); - const QJsonObject pos = outputInfo["pos"].toObject(); - props->pos = QPoint(pos["x"].toInt(), pos["y"].toInt()); - if (const QJsonValue scale = outputInfo["scale"]; !scale.isUndefined()) { - props->scale = scale.toDouble(1.); - } - props->transform = KWinKScreenIntegration::toDrmTransform(outputInfo["rotation"].toInt()); - - props->overscan = static_cast(outputInfo["overscan"].toInt(props->overscan)); - props->vrrPolicy = static_cast(outputInfo["vrrpolicy"].toInt(static_cast(props->vrrPolicy))); - props->rgbRange = static_cast(outputInfo["rgbrange"].toInt(static_cast(props->rgbRange))); - - if (const QJsonObject modeInfo = outputInfo["mode"].toObject(); !modeInfo.isEmpty()) { - if (auto mode = KWinKScreenIntegration::parseMode(output, modeInfo)) { - props->mode = mode; - } - } - } else { - props->enabled = true; - props->pos = pos; - props->transform = DrmOutput::Transform::Normal; - } - pos.setX(pos.x() + output->geometry().width()); - } - bool allDisabled = std::all_of(outputs.begin(), outputs.end(), [&cfg](const auto &output) { - return !cfg.changeSet(output)->enabled; - }); - if (allDisabled) { - qCWarning(KWIN_DRM) << "KScreen config would disable all outputs!"; - return false; - } - if (!cfg.changeSet(primaryOutput)->enabled) { - qCWarning(KWIN_DRM) << "KScreen config would disable the primary output!"; - return false; - } - if (!applyOutputChanges(cfg)) { - qCWarning(KWIN_DRM) << "Applying KScreen config failed!"; - return false; - } - setPrimaryOutput(primaryOutput); - return true; -} - void DrmBackend::enableOutput(DrmAbstractOutput *output, bool enable) { if (m_enabledOutputs.contains(output) == enable) { @@ -611,7 +436,6 @@ QString DrmBackend::supportInformation() const Output *DrmBackend::createVirtualOutput(const QString &name, const QSize &size, double scale) { auto output = primaryGpu()->createVirtualOutput(name, size * scale, scale, DrmVirtualOutput::Type::Virtual); - readOutputsConfiguration(m_outputs); Q_EMIT screensQueried(); return output; } diff --git a/src/backends/drm/drm_backend.h b/src/backends/drm/drm_backend.h index 14edf70184..bde1708704 100644 --- a/src/backends/drm/drm_backend.h +++ b/src/backends/drm/drm_backend.h @@ -95,7 +95,6 @@ private: void activate(bool active); void reactivate(); void deactivate(); - bool readOutputsConfiguration(const QVector &outputs); void handleUdevEvent(); void removeGpu(DrmGpu *gpu); DrmGpu *addGpu(const QString &fileName); diff --git a/src/workspace.cpp b/src/workspace.cpp index c43c8d4e9f..7491cc3f4b 100644 --- a/src/workspace.cpp +++ b/src/workspace.cpp @@ -32,6 +32,7 @@ #include "moving_client_x11_filter.h" #include "netinfo.h" #include "outline.h" +#include "outputconfiguration.h" #include "placement.h" #include "platform.h" #include "pluginmanager.h" @@ -206,6 +207,11 @@ void Workspace::init() connect(options, &Options::separateScreenFocusChanged, m_focusChain.get(), &FocusChain::setSeparateScreenFocus); m_focusChain->setSeparateScreenFocus(options->isSeparateScreenFocus()); + if (waylandServer()) { + updateOutputConfiguration(); + connect(kwinApp()->platform(), &Platform::screensQueried, this, &Workspace::updateOutputConfiguration); + } + Platform *platform = kwinApp()->platform(); connect(platform, &Platform::outputEnabled, this, &Workspace::slotOutputEnabled); connect(platform, &Platform::outputDisabled, this, &Workspace::slotOutputDisabled); @@ -495,6 +501,181 @@ Workspace::~Workspace() _self = nullptr; } +namespace KWinKScreenIntegration +{ +/// See KScreen::Output::hashMd5 +QString outputHash(Output *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()); +} + +/// See KScreen::Config::connectedOutputsHash in libkscreen +QString connectedOutputsHash(const QVector &outputs) +{ + QStringList hashedOutputs; + hashedOutputs.reserve(outputs.count()); + for (auto output : qAsConst(outputs)) { + if (!output->isPlaceholder() && !output->isNonDesktop()) { + 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 outputsConfig(const QVector &outputs, const QString &hash) +{ + const QString kscreenJsonPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kscreen/") % hash); + if (kscreenJsonPath.isEmpty()) { + return {}; + } + + QFile f(kscreenJsonPath); + if (!f.open(QIODevice::ReadOnly)) { + qCWarning(KWIN_CORE) << "Could not open file" << kscreenJsonPath; + return {}; + } + + QJsonParseError error; + const auto doc = QJsonDocument::fromJson(f.readAll(), &error); + if (error.error != QJsonParseError::NoError) { + qCWarning(KWIN_CORE) << "Failed to parse" << kscreenJsonPath << error.errorString(); + return {}; + } + + QMap 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, +}; + +Output::Transform toDrmTransform(int rotation) +{ + switch (Rotation(rotation)) { + case None: + return Output::Transform::Normal; + case Left: + return Output::Transform::Rotated90; + case Inverted: + return Output::Transform::Rotated180; + case Right: + return Output::Transform::Rotated270; + default: + Q_UNREACHABLE(); + } +} + +std::shared_ptr parseMode(Output *output, const QJsonObject &modeInfo) +{ + const QJsonObject size = modeInfo["size"].toObject(); + const QSize modeSize = QSize(size["width"].toInt(), size["height"].toInt()); + const int refreshRate = std::round(modeInfo["refresh"].toDouble() * 1000); + + const auto modes = output->modes(); + auto it = std::find_if(modes.begin(), modes.end(), [&modeSize, &refreshRate](const auto &mode) { + return mode->size() == modeSize && mode->refreshRate() == refreshRate; + }); + return (it != modes.end()) ? *it : nullptr; +} +} + +void Workspace::updateOutputConfiguration() +{ + // There's conflict between this code and setVirtualOutputs(), need to adjust the tests. + if (QStandardPaths::isTestModeEnabled()) { + return; + } + + const auto outputs = kwinApp()->platform()->outputs(); + const QString hash = KWinKScreenIntegration::connectedOutputsHash(outputs); + if (m_outputsHash == hash) { + return; + } + + const auto outputsInfo = KWinKScreenIntegration::outputsConfig(outputs, hash); + m_outputsHash = hash; + + Output *primaryOutput = outputs.constFirst(); + OutputConfiguration cfg; + // default position goes from left to right + QPoint pos(0, 0); + for (const auto &output : qAsConst(outputs)) { + if (output->isPlaceholder() || output->isNonDesktop()) { + continue; + } + auto props = cfg.changeSet(output); + const QJsonObject outputInfo = outputsInfo[output]; + qCDebug(KWIN_CORE) << "Reading output configuration for " << output; + if (!outputInfo.isEmpty()) { + if (outputInfo["primary"].toBool()) { + primaryOutput = output; + } + props->enabled = outputInfo["enabled"].toBool(true); + const QJsonObject pos = outputInfo["pos"].toObject(); + props->pos = QPoint(pos["x"].toInt(), pos["y"].toInt()); + if (const QJsonValue scale = outputInfo["scale"]; !scale.isUndefined()) { + props->scale = scale.toDouble(1.); + } + props->transform = KWinKScreenIntegration::toDrmTransform(outputInfo["rotation"].toInt()); + + props->overscan = static_cast(outputInfo["overscan"].toInt(props->overscan)); + props->vrrPolicy = static_cast(outputInfo["vrrpolicy"].toInt(static_cast(props->vrrPolicy))); + props->rgbRange = static_cast(outputInfo["rgbrange"].toInt(static_cast(props->rgbRange))); + + if (const QJsonObject modeInfo = outputInfo["mode"].toObject(); !modeInfo.isEmpty()) { + if (auto mode = KWinKScreenIntegration::parseMode(output, modeInfo)) { + props->mode = mode; + } + } + } else { + props->enabled = true; + props->pos = pos; + props->transform = Output::Transform::Normal; + } + pos.setX(pos.x() + output->geometry().width()); + } + bool allDisabled = std::all_of(outputs.begin(), outputs.end(), [&cfg](const auto &output) { + return !cfg.changeSet(output)->enabled; + }); + if (allDisabled) { + qCWarning(KWIN_CORE) << "KScreen config would disable all outputs!"; + return; + } + if (!cfg.changeSet(primaryOutput)->enabled) { + qCWarning(KWIN_CORE) << "KScreen config would disable the primary output!"; + return; + } + if (!kwinApp()->platform()->applyOutputChanges(cfg)) { + qCWarning(KWIN_CORE) << "Applying KScreen config failed!"; + return; + } + kwinApp()->platform()->setPrimaryOutput(primaryOutput); +} + void Workspace::setupWindowConnections(Window *window) { connect(window, &Window::desktopPresenceChanged, this, &Workspace::desktopPresenceChanged); diff --git a/src/workspace.h b/src/workspace.h index 6740075d50..44b6042bb1 100644 --- a/src/workspace.h +++ b/src/workspace.h @@ -626,6 +626,8 @@ private: Window *findWindowToActivateOnDesktop(VirtualDesktop *desktop); void removeWindow(Window *window); + void updateOutputConfiguration(); + struct Constraint { Window *below; @@ -648,6 +650,8 @@ private: QList m_outputs; Output *m_activeOutput = nullptr; + QString m_outputsHash; + Window *m_activeWindow; Window *m_lastActiveWindow; Window *m_moveResizeWindow;