File indexing completed on 2024-05-05 05:34:20

0001 /*
0002  *  SPDX-FileCopyrightText: 2012-2014 Daniel Vrátil <dvratil@redhat.com>
0003  *
0004  *  SPDX-License-Identifier: LGPL-2.1-or-later
0005  */
0006 
0007 #include "configmonitor.h"
0008 #include "abstractbackend.h"
0009 #include "backendinterface.h"
0010 #include "backendmanager_p.h"
0011 #include "configserializer_p.h"
0012 #include "getconfigoperation.h"
0013 #include "kscreen_debug.h"
0014 #include "output.h"
0015 
0016 #include <QDBusPendingCallWatcher>
0017 
0018 using namespace KScreen;
0019 
0020 class Q_DECL_HIDDEN ConfigMonitor::Private : public QObject
0021 {
0022     Q_OBJECT
0023 
0024 public:
0025     Private(ConfigMonitor *q);
0026 
0027     void onBackendReady(org::kde::kscreen::Backend *backend);
0028     void backendConfigChanged(const QVariantMap &configMap);
0029     void configDestroyed(QObject *removedConfig);
0030     void getConfigFinished(ConfigOperation *op);
0031     void updateConfigs(const KScreen::ConfigPtr &newConfig);
0032     void edidReady(QDBusPendingCallWatcher *watcher);
0033 
0034     QList<QWeakPointer<KScreen::Config>> watchedConfigs;
0035 
0036     QPointer<org::kde::kscreen::Backend> mBackend;
0037     bool mFirstBackend;
0038 
0039     QMap<KScreen::ConfigPtr, QList<int>> mPendingEDIDRequests;
0040 
0041 private:
0042     ConfigMonitor *q;
0043 };
0044 
0045 ConfigMonitor::Private::Private(ConfigMonitor *q)
0046     : QObject(q)
0047     , mFirstBackend(true)
0048     , q(q)
0049 {
0050 }
0051 
0052 void ConfigMonitor::Private::onBackendReady(org::kde::kscreen::Backend *backend)
0053 {
0054     Q_ASSERT(BackendManager::instance()->method() == BackendManager::OutOfProcess);
0055     if (backend == mBackend) {
0056         return;
0057     }
0058 
0059     if (mBackend) {
0060         disconnect(mBackend.data(), &org::kde::kscreen::Backend::configChanged, this, &ConfigMonitor::Private::backendConfigChanged);
0061     }
0062 
0063     mBackend = QPointer<org::kde::kscreen::Backend>(backend);
0064     // If we received a new backend interface, then it's very likely that it is
0065     // because the backend process has crashed - just to be sure we haven't missed
0066     // any change, request the current config now and update our watched configs
0067     //
0068     // Only request the config if this is not initial backend request, because it
0069     // can happen that if a change happened before now, or before we get the config,
0070     // the result will be invalid. This can happen when KScreen KDED launches and
0071     // detects changes need to be done.
0072     if (!mFirstBackend && !watchedConfigs.isEmpty()) {
0073         connect(new GetConfigOperation(), &GetConfigOperation::finished, this, &Private::getConfigFinished);
0074     }
0075     mFirstBackend = false;
0076 
0077     connect(mBackend.data(), &org::kde::kscreen::Backend::configChanged, this, &ConfigMonitor::Private::backendConfigChanged);
0078 }
0079 
0080 void ConfigMonitor::Private::getConfigFinished(ConfigOperation *op)
0081 {
0082     Q_ASSERT(BackendManager::instance()->method() == BackendManager::OutOfProcess);
0083     if (op->hasError()) {
0084         qCWarning(KSCREEN) << "Failed to retrieve current config: " << op->errorString();
0085         return;
0086     }
0087 
0088     const KScreen::ConfigPtr config = qobject_cast<GetConfigOperation *>(op)->config();
0089     updateConfigs(config);
0090 }
0091 
0092 void ConfigMonitor::Private::backendConfigChanged(const QVariantMap &configMap)
0093 {
0094     Q_ASSERT(BackendManager::instance()->method() == BackendManager::OutOfProcess);
0095     ConfigPtr newConfig = ConfigSerializer::deserializeConfig(configMap);
0096     if (!newConfig) {
0097         qCWarning(KSCREEN) << "Failed to deserialize config from DBus change notification";
0098         return;
0099     }
0100 
0101     const auto connectedOutputs = newConfig->connectedOutputs();
0102     for (const OutputPtr &output : connectedOutputs) {
0103         if (!output->edid() && output->isConnected()) {
0104             QDBusPendingReply<QByteArray> reply = mBackend->getEdid(output->id());
0105             mPendingEDIDRequests[newConfig].append(output->id());
0106             QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply);
0107             watcher->setProperty("outputId", output->id());
0108             watcher->setProperty("config", QVariant::fromValue(newConfig));
0109             connect(watcher, &QDBusPendingCallWatcher::finished, this, &ConfigMonitor::Private::edidReady);
0110         }
0111     }
0112 
0113     if (mPendingEDIDRequests.contains(newConfig)) {
0114         qCDebug(KSCREEN) << "Requesting missing EDID for outputs" << mPendingEDIDRequests[newConfig];
0115     } else {
0116         updateConfigs(newConfig);
0117     }
0118 }
0119 
0120 void ConfigMonitor::Private::edidReady(QDBusPendingCallWatcher *watcher)
0121 {
0122     Q_ASSERT(BackendManager::instance()->method() == BackendManager::OutOfProcess);
0123 
0124     const int outputId = watcher->property("outputId").toInt();
0125     const ConfigPtr config = watcher->property("config").value<KScreen::ConfigPtr>();
0126     Q_ASSERT(mPendingEDIDRequests.contains(config));
0127     Q_ASSERT(mPendingEDIDRequests[config].contains(outputId));
0128 
0129     watcher->deleteLater();
0130 
0131     mPendingEDIDRequests[config].removeOne(outputId);
0132 
0133     const QDBusPendingReply<QByteArray> reply = *watcher;
0134     if (reply.isError()) {
0135         qCWarning(KSCREEN) << "Error when retrieving EDID: " << reply.error().message();
0136     } else {
0137         const QByteArray edid = reply.argumentAt<0>();
0138         if (!edid.isEmpty()) {
0139             OutputPtr output = config->output(outputId);
0140             output->setEdid(edid);
0141         }
0142     }
0143 
0144     if (mPendingEDIDRequests[config].isEmpty()) {
0145         mPendingEDIDRequests.remove(config);
0146         updateConfigs(config);
0147     }
0148 }
0149 
0150 void ConfigMonitor::Private::updateConfigs(const KScreen::ConfigPtr &newConfig)
0151 {
0152     QMutableListIterator<QWeakPointer<Config>> iter(watchedConfigs);
0153     while (iter.hasNext()) {
0154         KScreen::ConfigPtr config = iter.next().toStrongRef();
0155         if (!config) {
0156             iter.remove();
0157             continue;
0158         }
0159 
0160         config->apply(newConfig);
0161         iter.setValue(config.toWeakRef());
0162     }
0163 
0164     Q_EMIT q->configurationChanged();
0165 }
0166 
0167 void ConfigMonitor::Private::configDestroyed(QObject *removedConfig)
0168 {
0169     for (auto iter = watchedConfigs.begin(); iter != watchedConfigs.end();) {
0170         if (iter->toStrongRef() == removedConfig) {
0171             iter = watchedConfigs.erase(iter);
0172             // Iterate over the entire list in case there are duplicates
0173         } else {
0174             ++iter;
0175         }
0176     }
0177 }
0178 
0179 ConfigMonitor *ConfigMonitor::instance()
0180 {
0181     static ConfigMonitor *s_instance = nullptr;
0182 
0183     if (s_instance == nullptr) {
0184         s_instance = new ConfigMonitor();
0185     }
0186 
0187     return s_instance;
0188 }
0189 
0190 ConfigMonitor::ConfigMonitor()
0191     : QObject()
0192     , d(new Private(this))
0193 {
0194     if (BackendManager::instance()->method() == BackendManager::OutOfProcess) {
0195         connect(BackendManager::instance(), &BackendManager::backendReady, d, &ConfigMonitor::Private::onBackendReady);
0196         BackendManager::instance()->requestBackend();
0197     }
0198 }
0199 
0200 ConfigMonitor::~ConfigMonitor()
0201 {
0202     delete d;
0203 }
0204 
0205 void ConfigMonitor::addConfig(const ConfigPtr &config)
0206 {
0207     const QWeakPointer<Config> weakConfig = config.toWeakRef();
0208     if (!d->watchedConfigs.contains(weakConfig)) {
0209         connect(weakConfig.toStrongRef().data(), &QObject::destroyed, d, &Private::configDestroyed);
0210         d->watchedConfigs << weakConfig;
0211     }
0212 }
0213 
0214 void ConfigMonitor::removeConfig(const ConfigPtr &config)
0215 {
0216     const QWeakPointer<Config> weakConfig = config.toWeakRef();
0217     if (d->watchedConfigs.contains(config)) {
0218         disconnect(weakConfig.toStrongRef().data(), &QObject::destroyed, d, &Private::configDestroyed);
0219         d->watchedConfigs.removeAll(config);
0220     }
0221 }
0222 
0223 void ConfigMonitor::connectInProcessBackend(KScreen::AbstractBackend *backend)
0224 {
0225     Q_ASSERT(BackendManager::instance()->method() == BackendManager::InProcess);
0226     connect(backend, &AbstractBackend::configChanged, [this](KScreen::ConfigPtr config) {
0227         if (config.isNull()) {
0228             return;
0229         }
0230         qCDebug(KSCREEN) << "Backend change!" << config;
0231         d->updateConfigs(config);
0232     });
0233 }
0234 
0235 #include "configmonitor.moc"
0236 
0237 #include "moc_configmonitor.cpp"