File indexing completed on 2024-04-21 04:43:15

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