| /* This file is part of the KDE project |
| Copyright (C) 2004-2007 Matthias Kretz <kretz@kde.org> |
| |
| This library is free software; you can redistribute it and/or |
| modify it under the terms of the GNU Lesser General Public |
| License as published by the Free Software Foundation; either |
| version 2.1 of the License, or (at your option) version 3, or any |
| later version accepted by the membership of KDE e.V. (or its |
| successor approved by the membership of KDE e.V.), Nokia Corporation |
| (or its successors, if any) and the KDE Free Qt Foundation, which shall |
| act as a proxy defined in Section 6 of version 3 of the license. |
| |
| This library is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| Lesser General Public License for more details. |
| |
| You should have received a copy of the GNU Lesser General Public |
| License along with this library. If not, see <http://www.gnu.org/licenses/>. |
| |
| */ |
| |
| #include "factory_p.h" |
| |
| #include "backendinterface.h" |
| #include "medianode_p.h" |
| #include "mediaobject.h" |
| #include "audiooutput.h" |
| #include "globalstatic_p.h" |
| #include "objectdescription.h" |
| #include "platformplugin.h" |
| #include "phononnamespace_p.h" |
| |
| #include <QtCore/QCoreApplication> |
| #include <QtCore/QDir> |
| #include <QtCore/QList> |
| #include <QtCore/QPluginLoader> |
| #include <QtCore/QPointer> |
| #ifndef QT_NO_DBUS |
| #include <QtDBus/QtDBus> |
| #endif |
| |
| QT_BEGIN_NAMESPACE |
| |
| namespace Phonon |
| { |
| |
| class PlatformPlugin; |
| class FactoryPrivate : public Phonon::Factory::Sender |
| { |
| friend QObject *Factory::backend(bool); |
| Q_OBJECT |
| public: |
| FactoryPrivate(); |
| ~FactoryPrivate(); |
| bool createBackend(); |
| #ifndef QT_NO_PHONON_PLATFORMPLUGIN |
| PlatformPlugin *platformPlugin(); |
| |
| PlatformPlugin *m_platformPlugin; |
| bool m_noPlatformPlugin; |
| #endif //QT_NO_PHONON_PLATFORMPLUGIN |
| QPointer<QObject> m_backendObject; |
| |
| QList<QObject *> objects; |
| QList<MediaNodePrivate *> mediaNodePrivateList; |
| |
| private Q_SLOTS: |
| /** |
| * This is called via DBUS when the user changes the Phonon Backend. |
| */ |
| #ifndef QT_NO_DBUS |
| void phononBackendChanged(); |
| #endif //QT_NO_DBUS |
| |
| /** |
| * unregisters the backend object |
| */ |
| void objectDestroyed(QObject *); |
| |
| void objectDescriptionChanged(ObjectDescriptionType); |
| }; |
| |
| PHONON_GLOBAL_STATIC(Phonon::FactoryPrivate, globalFactory) |
| |
| static inline void ensureLibraryPathSet() |
| { |
| #ifdef PHONON_LIBRARY_PATH |
| static bool done = false; |
| if (!done) { |
| done = true; |
| QCoreApplication::addLibraryPath(QLatin1String(PHONON_LIBRARY_PATH)); |
| } |
| #endif // PHONON_LIBRARY_PATH |
| } |
| |
| void Factory::setBackend(QObject *b) |
| { |
| Q_ASSERT(globalFactory->m_backendObject == 0); |
| globalFactory->m_backendObject = b; |
| } |
| |
| /*void Factory::createBackend(const QString &library, const QString &version) |
| { |
| Q_ASSERT(globalFactory->m_backendObject == 0); |
| PlatformPlugin *f = globalFactory->platformPlugin(); |
| if (f) { |
| globalFactory->m_backendObject = f->createBackend(library, version); |
| } |
| }*/ |
| |
| bool FactoryPrivate::createBackend() |
| { |
| #ifndef QT_NO_LIBRARY |
| Q_ASSERT(m_backendObject == 0); |
| #ifndef QT_NO_PHONON_PLATFORMPLUGIN |
| PlatformPlugin *f = globalFactory->platformPlugin(); |
| if (f) { |
| m_backendObject = f->createBackend(); |
| } |
| #endif //QT_NO_PHONON_PLATFORMPLUGIN |
| if (!m_backendObject) { |
| ensureLibraryPathSet(); |
| |
| // could not load a backend through the platform plugin. Falling back to the default |
| // (finding the first loadable backend). |
| const QLatin1String suffix("/phonon_backend/"); |
| const QStringList paths = QCoreApplication::libraryPaths(); |
| for (int i = 0; i < paths.count(); ++i) { |
| const QString libPath = paths.at(i) + suffix; |
| const QDir dir(libPath); |
| if (!dir.exists()) { |
| pDebug() << Q_FUNC_INFO << dir.absolutePath() << "does not exist"; |
| continue; |
| } |
| |
| QStringList plugins(dir.entryList(QDir::Files)); |
| |
| #ifdef Q_OS_SYMBIAN |
| static const QString preferredPluginName = QLatin1String("phonon_mmf"); |
| const int preferredPluginIndex = plugins.indexOf(preferredPluginName + ".qtplugin"); |
| if (preferredPluginIndex != -1) |
| plugins.move(preferredPluginIndex, 0); |
| #endif |
| |
| const QStringList files = dir.entryList(QDir::Files); |
| for (int i = 0; i < plugins.count(); ++i) { |
| QPluginLoader pluginLoader(libPath + plugins.at(i)); |
| if (!pluginLoader.load()) { |
| pDebug() << Q_FUNC_INFO << " load failed:" |
| << pluginLoader.errorString(); |
| continue; |
| } |
| pDebug() << pluginLoader.instance(); |
| m_backendObject = pluginLoader.instance(); |
| if (m_backendObject) { |
| break; |
| } |
| |
| // no backend found, don't leave an unused plugin in memory |
| pluginLoader.unload(); |
| } |
| |
| if (m_backendObject) { |
| break; |
| } |
| } |
| if (!m_backendObject) { |
| pWarning() << Q_FUNC_INFO << "phonon backend plugin could not be loaded"; |
| return false; |
| } |
| } |
| |
| connect(m_backendObject, SIGNAL(objectDescriptionChanged(ObjectDescriptionType)), |
| SLOT(objectDescriptionChanged(ObjectDescriptionType))); |
| |
| return true; |
| #else //QT_NO_LIBRARY |
| pWarning() << Q_FUNC_INFO << "Trying to use Phonon with QT_NO_LIBRARY defined. " |
| "That is currently not supported"; |
| return false; |
| #endif |
| } |
| |
| FactoryPrivate::FactoryPrivate() |
| : |
| #ifndef QT_NO_PHONON_PLATFORMPLUGIN |
| m_platformPlugin(0), |
| m_noPlatformPlugin(false), |
| #endif //QT_NO_PHONON_PLATFORMPLUGIN |
| m_backendObject(0) |
| { |
| // Add the post routine to make sure that all other global statics (especially the ones from Qt) |
| // are still available. If the FactoryPrivate dtor is called too late many bad things can happen |
| // as the whole backend might still be alive. |
| qAddPostRoutine(globalFactory.destroy); |
| #ifndef QT_NO_DBUS |
| QDBusConnection::sessionBus().connect(QString(), QString(), QLatin1String("org.kde.Phonon.Factory"), |
| QLatin1String("phononBackendChanged"), this, SLOT(phononBackendChanged())); |
| #endif |
| } |
| |
| FactoryPrivate::~FactoryPrivate() |
| { |
| for (int i = 0; i < mediaNodePrivateList.count(); ++i) { |
| mediaNodePrivateList.at(i)->deleteBackendObject(); |
| } |
| if (objects.size() > 0) { |
| pError() << "The backend objects are not deleted as was requested."; |
| qDeleteAll(objects); |
| } |
| delete m_backendObject; |
| #ifndef QT_NO_PHONON_PLATFORMPLUGIN |
| delete m_platformPlugin; |
| #endif //QT_NO_PHONON_PLATFORMPLUGIN |
| } |
| |
| void FactoryPrivate::objectDescriptionChanged(ObjectDescriptionType type) |
| { |
| #ifdef PHONON_METHODTEST |
| Q_UNUSED(type); |
| #else |
| pDebug() << Q_FUNC_INFO << type; |
| switch (type) { |
| case AudioOutputDeviceType: |
| emit availableAudioOutputDevicesChanged(); |
| break; |
| case AudioCaptureDeviceType: |
| emit availableAudioCaptureDevicesChanged(); |
| break; |
| default: |
| break; |
| } |
| //emit capabilitiesChanged(); |
| #endif // PHONON_METHODTEST |
| } |
| |
| Factory::Sender *Factory::sender() |
| { |
| return globalFactory; |
| } |
| |
| bool Factory::isMimeTypeAvailable(const QString &mimeType) |
| { |
| #ifndef QT_NO_PHONON_PLATFORMPLUGIN |
| PlatformPlugin *f = globalFactory->platformPlugin(); |
| if (f) { |
| return f->isMimeTypeAvailable(mimeType); |
| } |
| #else |
| Q_UNUSED(mimeType); |
| #endif //QT_NO_PHONON_PLATFORMPLUGIN |
| return true; // the MIME type might be supported, let BackendCapabilities find out |
| } |
| |
| void Factory::registerFrontendObject(MediaNodePrivate *bp) |
| { |
| globalFactory->mediaNodePrivateList.prepend(bp); // inserted last => deleted first |
| } |
| |
| void Factory::deregisterFrontendObject(MediaNodePrivate *bp) |
| { |
| // The Factory can already be cleaned up while there are other frontend objects still alive. |
| // When those are deleted they'll call deregisterFrontendObject through ~BasePrivate |
| if (!globalFactory.isDestroyed()) { |
| globalFactory->mediaNodePrivateList.removeAll(bp); |
| } |
| } |
| |
| #ifndef QT_NO_DBUS |
| void FactoryPrivate::phononBackendChanged() |
| { |
| if (m_backendObject) { |
| for (int i = 0; i < mediaNodePrivateList.count(); ++i) { |
| mediaNodePrivateList.at(i)->deleteBackendObject(); |
| } |
| if (objects.size() > 0) { |
| pDebug() << "WARNING: we were asked to change the backend but the application did\n" |
| "not free all references to objects created by the factory. Therefore we can not\n" |
| "change the backend without crashing. Now we have to wait for a restart to make\n" |
| "backendswitching possible."; |
| // in case there were objects deleted give 'em a chance to recreate |
| // them now |
| for (int i = 0; i < mediaNodePrivateList.count(); ++i) { |
| mediaNodePrivateList.at(i)->createBackendObject(); |
| } |
| return; |
| } |
| delete m_backendObject; |
| m_backendObject = 0; |
| } |
| createBackend(); |
| for (int i = 0; i < mediaNodePrivateList.count(); ++i) { |
| mediaNodePrivateList.at(i)->createBackendObject(); |
| } |
| emit backendChanged(); |
| } |
| #endif //QT_NO_DBUS |
| |
| //X void Factory::freeSoundcardDevices() |
| //X { |
| //X if (globalFactory->backend) { |
| //X globalFactory->backend->freeSoundcardDevices(); |
| //X } |
| //X } |
| |
| void FactoryPrivate::objectDestroyed(QObject * obj) |
| { |
| //pDebug() << Q_FUNC_INFO << obj; |
| objects.removeAll(obj); |
| } |
| |
| #define FACTORY_IMPL(classname) \ |
| QObject *Factory::create ## classname(QObject *parent) \ |
| { \ |
| if (backend()) { \ |
| return registerQObject(qobject_cast<BackendInterface *>(backend())->createObject(BackendInterface::classname##Class, parent)); \ |
| } \ |
| return 0; \ |
| } |
| #define FACTORY_IMPL_1ARG(classname) \ |
| QObject *Factory::create ## classname(int arg1, QObject *parent) \ |
| { \ |
| if (backend()) { \ |
| return registerQObject(qobject_cast<BackendInterface *>(backend())->createObject(BackendInterface::classname##Class, parent, QList<QVariant>() << arg1)); \ |
| } \ |
| return 0; \ |
| } |
| |
| FACTORY_IMPL(MediaObject) |
| #ifndef QT_NO_PHONON_EFFECT |
| FACTORY_IMPL_1ARG(Effect) |
| #endif //QT_NO_PHONON_EFFECT |
| #ifndef QT_NO_PHONON_VOLUMEFADEREFFECT |
| FACTORY_IMPL(VolumeFaderEffect) |
| #endif //QT_NO_PHONON_VOLUMEFADEREFFECT |
| FACTORY_IMPL(AudioOutput) |
| #ifndef QT_NO_PHONON_VIDEO |
| FACTORY_IMPL(VideoWidget) |
| #endif //QT_NO_PHONON_VIDEO |
| FACTORY_IMPL(AudioDataOutput) |
| |
| #undef FACTORY_IMPL |
| |
| #ifndef QT_NO_PHONON_PLATFORMPLUGIN |
| PlatformPlugin *FactoryPrivate::platformPlugin() |
| { |
| if (m_platformPlugin) { |
| return m_platformPlugin; |
| } |
| if (m_noPlatformPlugin) { |
| return 0; |
| } |
| #ifndef QT_NO_DBUS |
| if (!QCoreApplication::instance() || QCoreApplication::applicationName().isEmpty()) { |
| pWarning() << "Phonon needs QCoreApplication::applicationName to be set to export audio output names through the DBUS interface"; |
| } |
| #endif |
| Q_ASSERT(QCoreApplication::instance()); |
| const QByteArray platform_plugin_env = qgetenv("PHONON_PLATFORMPLUGIN"); |
| if (!platform_plugin_env.isEmpty()) { |
| QPluginLoader pluginLoader(QString::fromLocal8Bit(platform_plugin_env.constData())); |
| if (pluginLoader.load()) { |
| m_platformPlugin = qobject_cast<PlatformPlugin *>(pluginLoader.instance()); |
| if (m_platformPlugin) { |
| return m_platformPlugin; |
| } |
| } |
| } |
| const QString suffix(QLatin1String("/phonon_platform/")); |
| ensureLibraryPathSet(); |
| QDir dir; |
| dir.setNameFilters( |
| !qgetenv("KDE_FULL_SESSION").isEmpty() ? QStringList(QLatin1String("kde.*")) : |
| (!qgetenv("GNOME_DESKTOP_SESSION_ID").isEmpty() ? QStringList(QLatin1String("gnome.*")) : |
| QStringList()) |
| ); |
| dir.setFilter(QDir::Files); |
| const QStringList libPaths = QCoreApplication::libraryPaths(); |
| forever { |
| for (int i = 0; i < libPaths.count(); ++i) { |
| const QString libPath = libPaths.at(i) + suffix; |
| dir.setPath(libPath); |
| if (!dir.exists()) { |
| continue; |
| } |
| const QStringList files = dir.entryList(QDir::Files); |
| for (int i = 0; i < files.count(); ++i) { |
| QPluginLoader pluginLoader(libPath + files.at(i)); |
| if (!pluginLoader.load()) { |
| pDebug() << Q_FUNC_INFO << " platform plugin load failed:" |
| << pluginLoader.errorString(); |
| continue; |
| } |
| pDebug() << pluginLoader.instance(); |
| QObject *qobj = pluginLoader.instance(); |
| m_platformPlugin = qobject_cast<PlatformPlugin *>(qobj); |
| pDebug() << m_platformPlugin; |
| if (m_platformPlugin) { |
| connect(qobj, SIGNAL(objectDescriptionChanged(ObjectDescriptionType)), |
| SLOT(objectDescriptionChanged(ObjectDescriptionType))); |
| return m_platformPlugin; |
| } else { |
| delete qobj; |
| pDebug() << Q_FUNC_INFO << dir.absolutePath() << "exists but the platform plugin was not loadable:" << pluginLoader.errorString(); |
| pluginLoader.unload(); |
| } |
| } |
| } |
| if (dir.nameFilters().isEmpty()) { |
| break; |
| } |
| dir.setNameFilters(QStringList()); |
| } |
| pDebug() << Q_FUNC_INFO << "platform plugin could not be loaded"; |
| m_noPlatformPlugin = true; |
| return 0; |
| } |
| |
| PlatformPlugin *Factory::platformPlugin() |
| { |
| return globalFactory->platformPlugin(); |
| } |
| #endif // QT_NO_PHONON_PLATFORMPLUGIN |
| |
| QObject *Factory::backend(bool createWhenNull) |
| { |
| if (globalFactory.isDestroyed()) { |
| return 0; |
| } |
| if (createWhenNull && globalFactory->m_backendObject == 0) { |
| globalFactory->createBackend(); |
| // XXX: might create "reentrancy" problems: |
| // a method calls this method and is called again because the |
| // backendChanged signal is emitted |
| emit globalFactory->backendChanged(); |
| } |
| return globalFactory->m_backendObject; |
| } |
| |
| #ifndef QT_NO_PROPERTIES |
| #define GET_STRING_PROPERTY(name) \ |
| QString Factory::name() \ |
| { \ |
| if (globalFactory->m_backendObject) { \ |
| return globalFactory->m_backendObject->property(#name).toString(); \ |
| } \ |
| return QString(); \ |
| } \ |
| |
| GET_STRING_PROPERTY(identifier) |
| GET_STRING_PROPERTY(backendName) |
| GET_STRING_PROPERTY(backendComment) |
| GET_STRING_PROPERTY(backendVersion) |
| GET_STRING_PROPERTY(backendIcon) |
| GET_STRING_PROPERTY(backendWebsite) |
| #endif //QT_NO_PROPERTIES |
| QObject *Factory::registerQObject(QObject *o) |
| { |
| if (o) { |
| QObject::connect(o, SIGNAL(destroyed(QObject *)), globalFactory, SLOT(objectDestroyed(QObject *)), Qt::DirectConnection); |
| globalFactory->objects.append(o); |
| } |
| return o; |
| } |
| |
| } //namespace Phonon |
| |
| QT_END_NAMESPACE |
| |
| #include "factory.moc" |
| #include "moc_factory_p.cpp" |
| |
| // vim: sw=4 ts=4 |