File indexing completed on 2024-04-28 16:01:46
0001 /* 0002 Copyright (C) 2004-2007 Matthias Kretz <kretz@kde.org> 0003 Copyright (C) 2011 Harald Sitter <sitter@kde.org> 0004 0005 This library is free software; you can redistribute it and/or 0006 modify it under the terms of the GNU Lesser General Public 0007 License as published by the Free Software Foundation; either 0008 version 2.1 of the License, or (at your option) version 3, or any 0009 later version accepted by the membership of KDE e.V. (or its 0010 successor approved by the membership of KDE e.V.), Nokia Corporation 0011 (or its successors, if any) and the KDE Free Qt Foundation, which shall 0012 act as a proxy defined in Section 6 of version 3 of the license. 0013 0014 This library is distributed in the hope that it will be useful, 0015 but WITHOUT ANY WARRANTY; without even the implied warranty of 0016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0017 Lesser General Public License for more details. 0018 0019 You should have received a copy of the GNU Lesser General Public 0020 License along with this library. If not, see <http://www.gnu.org/licenses/>. 0021 */ 0022 0023 #include "factory_p.h" 0024 0025 #include "backendinterface.h" 0026 #include "medianode_p.h" 0027 #include "mediaobject.h" 0028 #include "audiooutput.h" 0029 #include "globalstatic_p.h" 0030 #include "objectdescription.h" 0031 #include "platformplugin.h" 0032 #include "phononconfig_p.h" 0033 #include "phononnamespace_p.h" 0034 0035 #include <QCoreApplication> 0036 #include <QDir> 0037 #include <QList> 0038 #include <QPluginLoader> 0039 #include <QPointer> 0040 #include <QSettings> 0041 #include <QApplication> 0042 #include <QMessageBox> 0043 #include <QString> 0044 0045 namespace Phonon 0046 { 0047 0048 class PlatformPlugin; 0049 class FactoryPrivate : public Phonon::Factory::Sender 0050 { 0051 friend QObject *Factory::backend(bool); 0052 Q_OBJECT 0053 public: 0054 FactoryPrivate(); 0055 ~FactoryPrivate() override; 0056 bool tryCreateBackend(const QString &path); 0057 bool createBackend(); 0058 #ifndef QT_NO_PHONON_PLATFORMPLUGIN 0059 PlatformPlugin *platformPlugin(); 0060 0061 PlatformPlugin *m_platformPlugin; 0062 bool m_noPlatformPlugin; 0063 #endif //QT_NO_PHONON_PLATFORMPLUGIN 0064 QPointer<QObject> m_backendObject; 0065 0066 QList<QObject *> objects; 0067 QList<MediaNodePrivate *> mediaNodePrivateList; 0068 0069 private Q_SLOTS: 0070 /** 0071 * unregisters the backend object 0072 */ 0073 void objectDestroyed(QObject *); 0074 0075 void objectDescriptionChanged(ObjectDescriptionType); 0076 }; 0077 0078 PHONON_GLOBAL_STATIC(Phonon::FactoryPrivate, globalFactory) 0079 0080 static inline void ensureLibraryPathSet() 0081 { 0082 #ifdef PHONON_PLUGIN_PATH 0083 static bool done = false; 0084 if (!done) { 0085 done = true; 0086 QCoreApplication::addLibraryPath(QLatin1String(PHONON_PLUGIN_PATH)); 0087 } 0088 #endif // PHONON_PLUGIN_PATH 0089 } 0090 0091 void Factory::setBackend(QObject *b) 0092 { 0093 Q_ASSERT(globalFactory->m_backendObject == nullptr); 0094 globalFactory->m_backendObject = b; 0095 } 0096 0097 bool FactoryPrivate::tryCreateBackend(const QString &path) 0098 { 0099 QPluginLoader pluginLoader(path); 0100 0101 pDebug() << "attempting to load" << path; 0102 if (!pluginLoader.load()) { 0103 pDebug() << Q_FUNC_INFO << " load failed:" << pluginLoader.errorString(); 0104 return false; 0105 } 0106 pDebug() << pluginLoader.instance(); 0107 m_backendObject = pluginLoader.instance(); 0108 if (m_backendObject) { 0109 return true; 0110 } 0111 0112 // no backend found, don't leave an unused plugin in memory 0113 pluginLoader.unload(); 0114 return false; 0115 } 0116 0117 // This entire function is so terrible to read I hope it implodes some day. 0118 bool FactoryPrivate::createBackend() 0119 { 0120 pDebug() << Q_FUNC_INFO << "Phonon" << PHONON_VERSION_STR << "trying to create backend..."; 0121 #ifndef QT_NO_LIBRARY 0122 Q_ASSERT(m_backendObject == nullptr); 0123 0124 // If the user defines a backend with PHONON_BACKEND this overrides the 0125 // platform plugin (because we cannot influence its lookup priority) and 0126 // consequently will try to find/load the defined backend manually. 0127 const QByteArray backendEnv = qgetenv("PHONON_BACKEND"); 0128 0129 #ifndef QT_NO_PHONON_PLATFORMPLUGIN 0130 PlatformPlugin *f = globalFactory->platformPlugin(); 0131 if (f && backendEnv.isEmpty()) { 0132 // TODO: it would be very groovy if we could add a param, so that the 0133 // platform could also try to load the defined backend as preferred choice. 0134 m_backendObject = f->createBackend(); 0135 } 0136 #endif //QT_NO_PHONON_PLATFORMPLUGIN 0137 0138 if (!m_backendObject) { 0139 const auto backends = Factory::findBackends(); 0140 0141 for (const auto &backend : backends) { 0142 if (tryCreateBackend(backend.pluginPath)) { 0143 break; 0144 } 0145 } 0146 0147 if (!m_backendObject) { 0148 pWarning() << Q_FUNC_INFO << "phonon backend plugin could not be loaded"; 0149 return false; 0150 } 0151 } 0152 0153 pDebug() << Q_FUNC_INFO 0154 << "Phonon backend" 0155 << m_backendObject->property("backendName").toString() 0156 << "version" 0157 << m_backendObject->property("backendVersion").toString() 0158 << "loaded"; 0159 0160 connect(m_backendObject, SIGNAL(objectDescriptionChanged(ObjectDescriptionType)), 0161 SLOT(objectDescriptionChanged(ObjectDescriptionType))); 0162 0163 return true; 0164 #else //QT_NO_LIBRARY 0165 pWarning() << Q_FUNC_INFO << "Trying to use Phonon with QT_NO_LIBRARY defined. " 0166 "That is currently not supported"; 0167 return false; 0168 #endif 0169 } 0170 0171 FactoryPrivate::FactoryPrivate() 0172 : 0173 #ifndef QT_NO_PHONON_PLATFORMPLUGIN 0174 m_platformPlugin(nullptr), 0175 m_noPlatformPlugin(false), 0176 #endif //QT_NO_PHONON_PLATFORMPLUGIN 0177 m_backendObject(nullptr) 0178 { 0179 // Add the post routine to make sure that all other global statics (especially the ones from Qt) 0180 // are still available. If the FactoryPrivate dtor is called too late many bad things can happen 0181 // as the whole backend might still be alive. 0182 qAddPostRoutine(globalFactory.destroy); 0183 } 0184 0185 FactoryPrivate::~FactoryPrivate() 0186 { 0187 for (int i = 0; i < mediaNodePrivateList.count(); ++i) { 0188 mediaNodePrivateList.at(i)->deleteBackendObject(); 0189 } 0190 if (objects.size() > 0) { 0191 pError() << "The backend objects are not deleted as was requested."; 0192 qDeleteAll(objects); 0193 } 0194 delete m_backendObject; 0195 #ifndef QT_NO_PHONON_PLATFORMPLUGIN 0196 delete m_platformPlugin; 0197 #endif //QT_NO_PHONON_PLATFORMPLUGIN 0198 } 0199 0200 void FactoryPrivate::objectDescriptionChanged(ObjectDescriptionType type) 0201 { 0202 #ifdef PHONON_METHODTEST 0203 Q_UNUSED(type); 0204 #else 0205 pDebug() << Q_FUNC_INFO << type; 0206 switch (type) { 0207 case AudioOutputDeviceType: 0208 emit availableAudioOutputDevicesChanged(); 0209 break; 0210 case AudioCaptureDeviceType: 0211 emit availableAudioCaptureDevicesChanged(); 0212 break; 0213 case VideoCaptureDeviceType: 0214 emit availableVideoCaptureDevicesChanged(); 0215 break; 0216 default: 0217 break; 0218 } 0219 //emit capabilitiesChanged(); 0220 #endif // PHONON_METHODTEST 0221 } 0222 0223 Factory::Sender *Factory::sender() 0224 { 0225 return globalFactory; 0226 } 0227 0228 bool Factory::isMimeTypeAvailable(const QString &mimeType) 0229 { 0230 #ifndef QT_NO_PHONON_PLATFORMPLUGIN 0231 PlatformPlugin *f = globalFactory->platformPlugin(); 0232 if (f) { 0233 return f->isMimeTypeAvailable(mimeType); 0234 } 0235 #else 0236 Q_UNUSED(mimeType); 0237 #endif //QT_NO_PHONON_PLATFORMPLUGIN 0238 return true; // the MIME type might be supported, let BackendCapabilities find out 0239 } 0240 0241 void Factory::registerFrontendObject(MediaNodePrivate *bp) 0242 { 0243 globalFactory->mediaNodePrivateList.prepend(bp); // inserted last => deleted first 0244 } 0245 0246 void Factory::deregisterFrontendObject(MediaNodePrivate *bp) 0247 { 0248 // The Factory can already be cleaned up while there are other frontend objects still alive. 0249 // When those are deleted they'll call deregisterFrontendObject through ~BasePrivate 0250 if (!globalFactory.isDestroyed()) { 0251 globalFactory->mediaNodePrivateList.removeAll(bp); 0252 } 0253 } 0254 0255 //X void Factory::freeSoundcardDevices() 0256 //X { 0257 //X if (globalFactory->backend) { 0258 //X globalFactory->backend->freeSoundcardDevices(); 0259 //X } 0260 //X } 0261 0262 void FactoryPrivate::objectDestroyed(QObject * obj) 0263 { 0264 //pDebug() << Q_FUNC_INFO << obj; 0265 objects.removeAll(obj); 0266 } 0267 0268 #define FACTORY_IMPL(classname) \ 0269 QObject *Factory::create ## classname(QObject *parent) \ 0270 { \ 0271 if (backend()) { \ 0272 return registerQObject(qobject_cast<BackendInterface *>(backend())->createObject(BackendInterface::classname##Class, parent)); \ 0273 } \ 0274 return nullptr; \ 0275 } 0276 #define FACTORY_IMPL_1ARG(classname) \ 0277 QObject *Factory::create ## classname(int arg1, QObject *parent) \ 0278 { \ 0279 if (backend()) { \ 0280 return registerQObject(qobject_cast<BackendInterface *>(backend())->createObject(BackendInterface::classname##Class, parent, QList<QVariant>() << arg1)); \ 0281 } \ 0282 return nullptr; \ 0283 } 0284 0285 FACTORY_IMPL(MediaObject) 0286 #ifndef QT_NO_PHONON_EFFECT 0287 FACTORY_IMPL_1ARG(Effect) 0288 #endif //QT_NO_PHONON_EFFECT 0289 #ifndef QT_NO_PHONON_VOLUMEFADEREFFECT 0290 FACTORY_IMPL(VolumeFaderEffect) 0291 #endif //QT_NO_PHONON_VOLUMEFADEREFFECT 0292 FACTORY_IMPL(AudioOutput) 0293 #ifndef QT_NO_PHONON_VIDEO 0294 FACTORY_IMPL(VideoWidget) 0295 // TODO P6: remove left overs from VGO. was removed except for factory references. 0296 FACTORY_IMPL(VideoGraphicsObject) 0297 #endif //QT_NO_PHONON_VIDEO 0298 FACTORY_IMPL(AudioDataOutput) 0299 0300 #undef FACTORY_IMPL 0301 0302 #ifndef QT_NO_PHONON_PLATFORMPLUGIN 0303 PlatformPlugin *FactoryPrivate::platformPlugin() 0304 { 0305 if (m_platformPlugin) { 0306 return m_platformPlugin; 0307 } 0308 if (m_noPlatformPlugin) { 0309 return nullptr; 0310 } 0311 Q_ASSERT(QCoreApplication::instance()); 0312 const QByteArray platform_plugin_env = qgetenv("PHONON_PLATFORMPLUGIN"); 0313 if (!platform_plugin_env.isEmpty()) { 0314 pDebug() << Q_FUNC_INFO << "platform plugin path:" << platform_plugin_env; 0315 QPluginLoader pluginLoader(QString::fromLocal8Bit(platform_plugin_env.constData())); 0316 if (pluginLoader.load()) { 0317 QObject *plInstance = pluginLoader.instance(); 0318 if (!plInstance) { 0319 pDebug() << Q_FUNC_INFO << "unable to grab root component object for the platform plugin"; 0320 } 0321 0322 m_platformPlugin = qobject_cast<PlatformPlugin *>(plInstance); 0323 if (m_platformPlugin) { 0324 pDebug() << Q_FUNC_INFO << "platform plugin" << m_platformPlugin->applicationName(); 0325 return m_platformPlugin; 0326 } else { 0327 pDebug() << Q_FUNC_INFO << "platform plugin cast fail" << plInstance; 0328 } 0329 } 0330 } 0331 const QString suffix(QLatin1String("/phonon_platform/")); 0332 ensureLibraryPathSet(); 0333 QDir dir; 0334 dir.setNameFilters( 0335 !qgetenv("KDE_FULL_SESSION").isEmpty() ? QStringList(QLatin1String("kde.*")) : 0336 (!qgetenv("GNOME_DESKTOP_SESSION_ID").isEmpty() ? QStringList(QLatin1String("gnome.*")) : 0337 QStringList()) 0338 ); 0339 dir.setFilter(QDir::Files); 0340 const QStringList libPaths = QCoreApplication::libraryPaths(); 0341 forever { 0342 for (int i = 0; i < libPaths.count(); ++i) { 0343 const QString libPath = libPaths.at(i) + suffix; 0344 dir.setPath(libPath); 0345 if (!dir.exists()) { 0346 continue; 0347 } 0348 const QStringList files = dir.entryList(QDir::Files); 0349 for (int i = 0; i < files.count(); ++i) { 0350 pDebug() << "attempting to load" << libPath + files.at(i); 0351 QPluginLoader pluginLoader(libPath + files.at(i)); 0352 if (!pluginLoader.load()) { 0353 pDebug() << Q_FUNC_INFO << " platform plugin load failed:" 0354 << pluginLoader.errorString(); 0355 continue; 0356 } 0357 pDebug() << pluginLoader.instance(); 0358 QObject *qobj = pluginLoader.instance(); 0359 m_platformPlugin = qobject_cast<PlatformPlugin *>(qobj); 0360 pDebug() << m_platformPlugin; 0361 if (m_platformPlugin) { 0362 connect(qobj, SIGNAL(objectDescriptionChanged(ObjectDescriptionType)), 0363 SLOT(objectDescriptionChanged(ObjectDescriptionType))); 0364 return m_platformPlugin; 0365 } else { 0366 delete qobj; 0367 pDebug() << Q_FUNC_INFO << dir.absolutePath() << "exists but the platform plugin was not loadable:" << pluginLoader.errorString(); 0368 pluginLoader.unload(); 0369 } 0370 } 0371 } 0372 if (dir.nameFilters().isEmpty()) { 0373 break; 0374 } 0375 dir.setNameFilters(QStringList()); 0376 } 0377 pDebug() << Q_FUNC_INFO << "platform plugin could not be loaded"; 0378 m_noPlatformPlugin = true; 0379 return nullptr; 0380 } 0381 0382 PlatformPlugin *Factory::platformPlugin() 0383 { 0384 return globalFactory->platformPlugin(); 0385 } 0386 #endif // QT_NO_PHONON_PLATFORMPLUGIN 0387 0388 QObject *Factory::backend(bool createWhenNull) 0389 { 0390 if (globalFactory.isDestroyed()) { 0391 return nullptr; 0392 } 0393 if (createWhenNull && globalFactory->m_backendObject == nullptr) { 0394 globalFactory->createBackend(); 0395 // XXX: might create "reentrancy" problems: 0396 // a method calls this method and is called again because the 0397 // backendChanged signal is emitted 0398 if (globalFactory->m_backendObject) { 0399 emit globalFactory->backendChanged(); 0400 } 0401 } 0402 return globalFactory->m_backendObject; 0403 } 0404 0405 #ifndef QT_NO_PROPERTIES 0406 #define GET_STRING_PROPERTY(name) \ 0407 QString Factory::name() \ 0408 { \ 0409 if (globalFactory->m_backendObject) { \ 0410 return globalFactory->m_backendObject->property(#name).toString(); \ 0411 } \ 0412 return QString(); \ 0413 } \ 0414 0415 GET_STRING_PROPERTY(identifier) 0416 GET_STRING_PROPERTY(backendName) 0417 GET_STRING_PROPERTY(backendComment) 0418 GET_STRING_PROPERTY(backendVersion) 0419 GET_STRING_PROPERTY(backendIcon) 0420 GET_STRING_PROPERTY(backendWebsite) 0421 #endif //QT_NO_PROPERTIES 0422 QObject *Factory::registerQObject(QObject *o) 0423 { 0424 if (o) { 0425 QObject::connect(o, SIGNAL(destroyed(QObject*)), globalFactory, SLOT(objectDestroyed(QObject*)), Qt::DirectConnection); 0426 globalFactory->objects.append(o); 0427 } 0428 return o; 0429 } 0430 0431 QList<BackendDescriptor> Factory::findBackends() 0432 { 0433 static QList<Phonon::BackendDescriptor> backendList; 0434 if (!backendList.isEmpty()) { 0435 return backendList; 0436 } 0437 ensureLibraryPathSet(); 0438 0439 QList<QString> iidPreference; 0440 QSettings settings("kde.org", "libphonon"); 0441 const int size = settings.beginReadArray("Backends"); 0442 for (int i = 0; i < size; ++i) { 0443 settings.setArrayIndex(i); 0444 iidPreference.append(settings.value("iid").toString()); 0445 } 0446 settings.endArray(); 0447 0448 // Load default preference list. 0449 const QStringList paths = QCoreApplication::libraryPaths(); 0450 for (const QString &path : paths) { 0451 const QString libPath = path + PHONON_BACKEND_DIR_SUFFIX; 0452 const QDir dir(libPath); 0453 if (!dir.exists()) { 0454 pDebug() << Q_FUNC_INFO << dir.absolutePath() << "does not exist"; 0455 continue; 0456 } 0457 0458 const QStringList plugins(dir.entryList(QDir::Files)); 0459 for (const QString &plugin : plugins) { 0460 Phonon::BackendDescriptor bd(libPath + plugin); 0461 if (!bd.isValid) { 0462 continue; 0463 } 0464 0465 const auto index = iidPreference.indexOf(bd.iid); 0466 if (index >= 0) { 0467 // Apply a weight. Weight strongly influences sort order. 0468 bd.weight = iidPreference.size() - index; 0469 } 0470 backendList.append(bd); 0471 } 0472 0473 std::sort(backendList.rbegin(), backendList.rend()); 0474 } 0475 0476 // Apply PHONON_BACKEND override if set. 0477 const QString backendEnv = qEnvironmentVariable("PHONON_BACKEND"); 0478 if (backendEnv.isEmpty()) { 0479 return backendList; 0480 } 0481 0482 for (int i = 0; i < backendList.size(); ++i) { 0483 const auto &backend = backendList.at(i); 0484 if (backendEnv == backend.pluginName) { 0485 backendList.move(i, 0); 0486 break; 0487 } 0488 } 0489 0490 return backendList; 0491 } 0492 0493 } //namespace Phonon 0494 0495 #include "factory.moc" 0496 #include "moc_factory_p.cpp" 0497 0498 // vim: sw=4 ts=4