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"