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;