File indexing completed on 2024-04-14 15:37:38

0001 /*
0002  * SPDX-FileCopyrightText: 2014 Daniel Vratil <dvratil@redhat.com>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.1-or-later
0005  *
0006  */
0007 
0008 #include "setconfigoperation.h"
0009 
0010 #include "abstractbackend.h"
0011 #include "backendmanager_p.h"
0012 #include "config.h"
0013 #include "configoperation_p.h"
0014 #include "configserializer_p.h"
0015 #include "kscreen_debug.h"
0016 #include "output.h"
0017 
0018 #include <QDBusPendingCall>
0019 #include <QDBusPendingCallWatcher>
0020 
0021 using namespace KScreen;
0022 
0023 namespace KScreen
0024 {
0025 class SetConfigOperationPrivate : public ConfigOperationPrivate
0026 {
0027     Q_OBJECT
0028 
0029 public:
0030     explicit SetConfigOperationPrivate(const KScreen::ConfigPtr &config, ConfigOperation *qq);
0031 
0032     void backendReady(org::kde::kscreen::Backend *backend) override;
0033     void onConfigSet(QDBusPendingCallWatcher *watcher);
0034     void normalizeOutputPositions();
0035     void fixPrimaryOutput();
0036 
0037     KScreen::ConfigPtr config;
0038 
0039 private:
0040     Q_DECLARE_PUBLIC(SetConfigOperation)
0041 };
0042 
0043 }
0044 
0045 SetConfigOperationPrivate::SetConfigOperationPrivate(const ConfigPtr &config, ConfigOperation *qq)
0046     : ConfigOperationPrivate(qq)
0047     , config(config)
0048 {
0049 }
0050 
0051 void SetConfigOperationPrivate::backendReady(org::kde::kscreen::Backend *backend)
0052 {
0053     ConfigOperationPrivate::backendReady(backend);
0054 
0055     Q_Q(SetConfigOperation);
0056 
0057     if (!backend) {
0058         q->setError(tr("Failed to prepare backend"));
0059         q->emitResult();
0060         return;
0061     }
0062 
0063     const QVariantMap map = ConfigSerializer::serializeConfig(config).toVariantMap();
0064     if (map.isEmpty()) {
0065         q->setError(tr("Failed to serialize request"));
0066         q->emitResult();
0067         return;
0068     }
0069 
0070     QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(backend->setConfig(map), this);
0071     connect(watcher, &QDBusPendingCallWatcher::finished, this, &SetConfigOperationPrivate::onConfigSet);
0072 }
0073 
0074 void SetConfigOperationPrivate::onConfigSet(QDBusPendingCallWatcher *watcher)
0075 {
0076     Q_Q(SetConfigOperation);
0077 
0078     QDBusPendingReply<QVariantMap> reply = *watcher;
0079     watcher->deleteLater();
0080 
0081     if (reply.isError()) {
0082         q->setError(reply.error().message());
0083         q->emitResult();
0084         return;
0085     }
0086 
0087     config = ConfigSerializer::deserializeConfig(reply.value());
0088     if (!config) {
0089         q->setError(tr("Failed to deserialize backend response"));
0090     }
0091 
0092     q->emitResult();
0093 }
0094 
0095 SetConfigOperation::SetConfigOperation(const ConfigPtr &config, QObject *parent)
0096     : ConfigOperation(new SetConfigOperationPrivate(config, this), parent)
0097 {
0098 }
0099 
0100 SetConfigOperation::~SetConfigOperation()
0101 {
0102 }
0103 
0104 ConfigPtr SetConfigOperation::config() const
0105 {
0106     Q_D(const SetConfigOperation);
0107     return d->config;
0108 }
0109 
0110 void SetConfigOperation::start()
0111 {
0112     Q_D(SetConfigOperation);
0113     d->normalizeOutputPositions();
0114     d->fixPrimaryOutput();
0115     if (BackendManager::instance()->method() == BackendManager::InProcess) {
0116         auto backend = d->loadBackend();
0117         backend->setConfig(d->config);
0118         emitResult();
0119     } else {
0120         d->requestBackend();
0121     }
0122 }
0123 
0124 void SetConfigOperationPrivate::normalizeOutputPositions()
0125 {
0126     if (!config) {
0127         return;
0128     }
0129     int offsetX = INT_MAX;
0130     int offsetY = INT_MAX;
0131     const auto outputs = config->outputs();
0132     for (const KScreen::OutputPtr &output : outputs) {
0133         if (!output->isPositionable()) {
0134             continue;
0135         }
0136         offsetX = qMin(output->pos().x(), offsetX);
0137         offsetY = qMin(output->pos().y(), offsetY);
0138     }
0139 
0140     if (!offsetX && !offsetY) {
0141         return;
0142     }
0143     qCDebug(KSCREEN) << "Correcting output positions by:" << QPoint(offsetX, offsetY);
0144     for (const KScreen::OutputPtr &output : outputs) {
0145         if (!output->isConnected() || !output->isEnabled()) {
0146             continue;
0147         }
0148         QPoint newPos = QPoint(output->pos().x() - offsetX, output->pos().y() - offsetY);
0149         qCDebug(KSCREEN) << "Moved output from" << output->pos() << "to" << newPos;
0150         output->setPos(newPos);
0151     }
0152 }
0153 
0154 void SetConfigOperationPrivate::fixPrimaryOutput()
0155 {
0156     if (!config || !(config->supportedFeatures() & Config::Feature::PrimaryDisplay)) {
0157         return;
0158     }
0159     const auto outputs = config->outputs();
0160     if (outputs.isEmpty()) {
0161         return;
0162     }
0163 
0164     // Here we make sure that:
0165     // - that our primary is enabled
0166     // - we have at least a primary
0167     // - we have exactly 1 primary
0168     // - we have a primary at all
0169     bool found = false;
0170     KScreen::OutputPtr primary;
0171     KScreen::OutputPtr candidate;
0172     for (const KScreen::OutputPtr &output : outputs) {
0173         if (output->isPrimary()) {
0174             if (!output->isEnabled()) {
0175                 qCDebug(KSCREEN) << "can't be primary if disabled!!" << output;
0176             } else if (found) {
0177                 qCDebug(KSCREEN) << "can only have 1 primary" << output;
0178             } else {
0179                 found = true;
0180                 primary = output;
0181             }
0182         } else if (output->isEnabled()) {
0183             candidate = output;
0184         }
0185     }
0186 
0187     if (!found && candidate) {
0188         qCDebug(KSCREEN) << "setting primary instead" << candidate;
0189         config->setPrimaryOutput(candidate);
0190     } else if (primary) {
0191         // ensures all others are set to non-primary. It's OK if all outputs
0192         // are disabled and `primary` is essentially nullptr.
0193         config->setPrimaryOutput(primary);
0194     }
0195 }
0196 
0197 #include "setconfigoperation.moc"