Phonon

factory.cpp
1/*
2 Copyright (C) 2004-2007 Matthias Kretz <kretz@kde.org>
3 Copyright (C) 2011 Harald Sitter <sitter@kde.org>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) version 3, or any
9 later version accepted by the membership of KDE e.V. (or its
10 successor approved by the membership of KDE e.V.), Nokia Corporation
11 (or its successors, if any) and the KDE Free Qt Foundation, which shall
12 act as a proxy defined in Section 6 of version 3 of the license.
13
14 This library is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
18
19 You should have received a copy of the GNU Lesser General Public
20 License along with this library. If not, see <http://www.gnu.org/licenses/>.
21*/
22
23#include "factory_p.h"
24
25#include "backendinterface.h"
26#include "medianode_p.h"
27#include "mediaobject.h"
28#include "audiooutput.h"
29#include "globalstatic_p.h"
30#include "objectdescription.h"
31#include "platformplugin.h"
32#include "phononconfig_p.h"
33#include "phononnamespace_p.h"
34
35#include <QCoreApplication>
36#include <QDir>
37#include <QList>
38#include <QPluginLoader>
39#include <QPointer>
40#include <QSettings>
41#include <QApplication>
42#include <QMessageBox>
43#include <QString>
44
45namespace Phonon
46{
47
48class PlatformPlugin;
49class FactoryPrivate : public Phonon::Factory::Sender
50{
51 friend QObject *Factory::backend(bool);
52 Q_OBJECT
53 public:
54 FactoryPrivate();
55 ~FactoryPrivate() override;
56 bool tryCreateBackend(const QString &path);
57 bool createBackend();
58#ifndef QT_NO_PHONON_PLATFORMPLUGIN
59 PlatformPlugin *platformPlugin();
60
61 PlatformPlugin *m_platformPlugin;
62 bool m_noPlatformPlugin;
63#endif //QT_NO_PHONON_PLATFORMPLUGIN
64 QPointer<QObject> m_backendObject;
65
66 QList<QObject *> objects;
67 QList<MediaNodePrivate *> mediaNodePrivateList;
68
69 private Q_SLOTS:
70 /**
71 * unregisters the backend object
72 */
73 void objectDestroyed(QObject *);
74
75 void objectDescriptionChanged(ObjectDescriptionType);
76};
77
78PHONON_GLOBAL_STATIC(Phonon::FactoryPrivate, globalFactory)
79
80static inline void ensureLibraryPathSet()
81{
82#ifdef PHONON_PLUGIN_PATH
83 static bool done = false;
84 if (!done) {
85 done = true;
87 }
88#endif // PHONON_PLUGIN_PATH
89}
90
91void Factory::setBackend(QObject *b)
92{
93 Q_ASSERT(globalFactory->m_backendObject == nullptr);
94 globalFactory->m_backendObject = b;
95}
96
97bool FactoryPrivate::tryCreateBackend(const QString &path)
98{
99 QPluginLoader pluginLoader(path);
100
101 pDebug() << "attempting to load" << path;
102 if (!pluginLoader.load()) {
103 pDebug() << Q_FUNC_INFO << " load failed:" << pluginLoader.errorString();
104 return false;
105 }
106 pDebug() << pluginLoader.instance();
107 m_backendObject = pluginLoader.instance();
108 if (m_backendObject) {
109 return true;
110 }
111
112 // no backend found, don't leave an unused plugin in memory
113 pluginLoader.unload();
114 return false;
115}
116
117// This entire function is so terrible to read I hope it implodes some day.
118bool FactoryPrivate::createBackend()
119{
120 pDebug() << Q_FUNC_INFO << "Phonon" << PHONON_VERSION_STR << "trying to create backend...";
121#ifndef QT_NO_LIBRARY
122 Q_ASSERT(m_backendObject == nullptr);
123
124 // If the user defines a backend with PHONON_BACKEND this overrides the
125 // platform plugin (because we cannot influence its lookup priority) and
126 // consequently will try to find/load the defined backend manually.
127 const QByteArray backendEnv = qgetenv("PHONON_BACKEND");
128
129#ifndef QT_NO_PHONON_PLATFORMPLUGIN
130 PlatformPlugin *f = globalFactory->platformPlugin();
131 if (f && backendEnv.isEmpty()) {
132 // TODO: it would be very groovy if we could add a param, so that the
133 // platform could also try to load the defined backend as preferred choice.
134 m_backendObject = f->createBackend();
135 }
136#endif //QT_NO_PHONON_PLATFORMPLUGIN
137
138 if (!m_backendObject) {
139 const auto backends = Factory::findBackends();
140
141 for (const auto &backend : backends) {
142 if (tryCreateBackend(backend.pluginPath)) {
143 break;
144 }
145 }
146
147 if (!m_backendObject) {
148 pWarning() << Q_FUNC_INFO << "phonon backend plugin could not be loaded";
149 return false;
150 }
151 }
152
153 pDebug() << Q_FUNC_INFO
154 << "Phonon backend"
155 << m_backendObject->property("backendName").toString()
156 << "version"
157 << m_backendObject->property("backendVersion").toString()
158 << "loaded";
159
160 connect(m_backendObject, SIGNAL(objectDescriptionChanged(ObjectDescriptionType)),
161 SLOT(objectDescriptionChanged(ObjectDescriptionType)));
162
163 return true;
164#else //QT_NO_LIBRARY
165 pWarning() << Q_FUNC_INFO << "Trying to use Phonon with QT_NO_LIBRARY defined. "
166 "That is currently not supported";
167 return false;
168#endif
169}
170
171FactoryPrivate::FactoryPrivate()
172 :
173#ifndef QT_NO_PHONON_PLATFORMPLUGIN
174 m_platformPlugin(nullptr),
175 m_noPlatformPlugin(false),
176#endif //QT_NO_PHONON_PLATFORMPLUGIN
177 m_backendObject(nullptr)
178{
179 // Add the post routine to make sure that all other global statics (especially the ones from Qt)
180 // are still available. If the FactoryPrivate dtor is called too late many bad things can happen
181 // as the whole backend might still be alive.
182 qAddPostRoutine(globalFactory.destroy);
183}
184
185FactoryPrivate::~FactoryPrivate()
186{
187 for (int i = 0; i < mediaNodePrivateList.count(); ++i) {
188 mediaNodePrivateList.at(i)->deleteBackendObject();
189 }
190 if (objects.size() > 0) {
191 pError() << "The backend objects are not deleted as was requested.";
192 qDeleteAll(objects);
193 }
194 delete m_backendObject;
195#ifndef QT_NO_PHONON_PLATFORMPLUGIN
196 delete m_platformPlugin;
197#endif //QT_NO_PHONON_PLATFORMPLUGIN
198}
199
200void FactoryPrivate::objectDescriptionChanged(ObjectDescriptionType type)
201{
202#ifdef PHONON_METHODTEST
203 Q_UNUSED(type);
204#else
205 pDebug() << Q_FUNC_INFO << type;
206 switch (type) {
207 case AudioOutputDeviceType:
208 emit availableAudioOutputDevicesChanged();
209 break;
210 case AudioCaptureDeviceType:
211 emit availableAudioCaptureDevicesChanged();
212 break;
213 case VideoCaptureDeviceType:
214 emit availableVideoCaptureDevicesChanged();
215 break;
216 default:
217 break;
218 }
219 //emit capabilitiesChanged();
220#endif // PHONON_METHODTEST
221}
222
223Factory::Sender *Factory::sender()
224{
225 return globalFactory;
226}
227
228bool Factory::isMimeTypeAvailable(const QString &mimeType)
229{
230#ifndef QT_NO_PHONON_PLATFORMPLUGIN
231 PlatformPlugin *f = globalFactory->platformPlugin();
232 if (f) {
233 return f->isMimeTypeAvailable(mimeType);
234 }
235#else
236 Q_UNUSED(mimeType);
237#endif //QT_NO_PHONON_PLATFORMPLUGIN
238 return true; // the MIME type might be supported, let BackendCapabilities find out
239}
240
241void Factory::registerFrontendObject(MediaNodePrivate *bp)
242{
243 globalFactory->mediaNodePrivateList.prepend(bp); // inserted last => deleted first
244}
245
246void Factory::deregisterFrontendObject(MediaNodePrivate *bp)
247{
248 // The Factory can already be cleaned up while there are other frontend objects still alive.
249 // When those are deleted they'll call deregisterFrontendObject through ~BasePrivate
250 if (!globalFactory.isDestroyed()) {
251 globalFactory->mediaNodePrivateList.removeAll(bp);
252 }
253}
254
255//X void Factory::freeSoundcardDevices()
256//X {
257//X if (globalFactory->backend) {
258//X globalFactory->backend->freeSoundcardDevices();
259//X }
260//X }
261
262void FactoryPrivate::objectDestroyed(QObject * obj)
263{
264 //pDebug() << Q_FUNC_INFO << obj;
265 objects.removeAll(obj);
266}
267
268#define FACTORY_IMPL(classname) \
269QObject *Factory::create ## classname(QObject *parent) \
270{ \
271 if (backend()) { \
272 return registerQObject(qobject_cast<BackendInterface *>(backend())->createObject(BackendInterface::classname##Class, parent)); \
273 } \
274 return nullptr; \
275}
276#define FACTORY_IMPL_1ARG(classname) \
277QObject *Factory::create ## classname(int arg1, QObject *parent) \
278{ \
279 if (backend()) { \
280 return registerQObject(qobject_cast<BackendInterface *>(backend())->createObject(BackendInterface::classname##Class, parent, QList<QVariant>() << arg1)); \
281 } \
282 return nullptr; \
283}
284
285FACTORY_IMPL(MediaObject)
286#ifndef QT_NO_PHONON_EFFECT
287FACTORY_IMPL_1ARG(Effect)
288#endif //QT_NO_PHONON_EFFECT
289#ifndef QT_NO_PHONON_VOLUMEFADEREFFECT
290FACTORY_IMPL(VolumeFaderEffect)
291#endif //QT_NO_PHONON_VOLUMEFADEREFFECT
292FACTORY_IMPL(AudioOutput)
293#ifndef QT_NO_PHONON_VIDEO
294FACTORY_IMPL(VideoWidget)
295// TODO P6: remove left overs from VGO. was removed except for factory references.
296FACTORY_IMPL(VideoGraphicsObject)
297#endif //QT_NO_PHONON_VIDEO
298FACTORY_IMPL(AudioDataOutput)
299
300#undef FACTORY_IMPL
301
302#ifndef QT_NO_PHONON_PLATFORMPLUGIN
303PlatformPlugin *FactoryPrivate::platformPlugin()
304{
305 if (m_platformPlugin) {
306 return m_platformPlugin;
307 }
308 if (m_noPlatformPlugin) {
309 return nullptr;
310 }
311 Q_ASSERT(QCoreApplication::instance());
312 const QByteArray platform_plugin_env = qgetenv("PHONON_PLATFORMPLUGIN");
313 if (!platform_plugin_env.isEmpty()) {
314 pDebug() << Q_FUNC_INFO << "platform plugin path:" << platform_plugin_env;
315 QPluginLoader pluginLoader(QString::fromLocal8Bit(platform_plugin_env.constData()));
316 if (pluginLoader.load()) {
317 QObject *plInstance = pluginLoader.instance();
318 if (!plInstance) {
319 pDebug() << Q_FUNC_INFO << "unable to grab root component object for the platform plugin";
320 }
321
322 m_platformPlugin = qobject_cast<PlatformPlugin *>(plInstance);
323 if (m_platformPlugin) {
324 pDebug() << Q_FUNC_INFO << "platform plugin" << m_platformPlugin->applicationName();
325 return m_platformPlugin;
326 } else {
327 pDebug() << Q_FUNC_INFO << "platform plugin cast fail" << plInstance;
328 }
329 }
330 }
331 const QString suffix(QLatin1String("/phonon_platform/"));
332 ensureLibraryPathSet();
333 QDir dir;
334 dir.setNameFilters(
335 !qgetenv("KDE_FULL_SESSION").isEmpty() ? QStringList(QLatin1String("kde.*")) :
336 (!qgetenv("GNOME_DESKTOP_SESSION_ID").isEmpty() ? QStringList(QLatin1String("gnome.*")) :
337 QStringList())
338 );
339 dir.setFilter(QDir::Files);
341 forever {
342 for (int i = 0; i < libPaths.count(); ++i) {
343 const QString libPath = libPaths.at(i) + suffix;
344 dir.setPath(libPath);
345 if (!dir.exists()) {
346 continue;
347 }
348 const QStringList files = dir.entryList(QDir::Files);
349 for (int i = 0; i < files.count(); ++i) {
350 pDebug() << "attempting to load" << libPath + files.at(i);
351 QPluginLoader pluginLoader(libPath + files.at(i));
352 if (!pluginLoader.load()) {
353 pDebug() << Q_FUNC_INFO << " platform plugin load failed:"
354 << pluginLoader.errorString();
355 continue;
356 }
357 pDebug() << pluginLoader.instance();
358 QObject *qobj = pluginLoader.instance();
359 m_platformPlugin = qobject_cast<PlatformPlugin *>(qobj);
360 pDebug() << m_platformPlugin;
361 if (m_platformPlugin) {
362 connect(qobj, SIGNAL(objectDescriptionChanged(ObjectDescriptionType)),
363 SLOT(objectDescriptionChanged(ObjectDescriptionType)));
364 return m_platformPlugin;
365 } else {
366 delete qobj;
367 pDebug() << Q_FUNC_INFO << dir.absolutePath() << "exists but the platform plugin was not loadable:" << pluginLoader.errorString();
368 pluginLoader.unload();
369 }
370 }
371 }
372 if (dir.nameFilters().isEmpty()) {
373 break;
374 }
375 dir.setNameFilters(QStringList());
376 }
377 pDebug() << Q_FUNC_INFO << "platform plugin could not be loaded";
378 m_noPlatformPlugin = true;
379 return nullptr;
380}
381
382PlatformPlugin *Factory::platformPlugin()
383{
384 return globalFactory->platformPlugin();
385}
386#endif // QT_NO_PHONON_PLATFORMPLUGIN
387
388QObject *Factory::backend(bool createWhenNull)
389{
390 if (globalFactory.isDestroyed()) {
391 return nullptr;
392 }
393 if (createWhenNull && globalFactory->m_backendObject == nullptr) {
394 globalFactory->createBackend();
395 // XXX: might create "reentrancy" problems:
396 // a method calls this method and is called again because the
397 // backendChanged signal is emitted
398 if (globalFactory->m_backendObject) {
399 emit globalFactory->backendChanged();
400 }
401 }
402 return globalFactory->m_backendObject;
403}
404
405#ifndef QT_NO_PROPERTIES
406#define GET_STRING_PROPERTY(name) \
407QString Factory::name() \
408{ \
409 if (globalFactory->m_backendObject) { \
410 return globalFactory->m_backendObject->property(#name).toString(); \
411 } \
412 return QString(); \
413} \
414
415GET_STRING_PROPERTY(identifier)
416GET_STRING_PROPERTY(backendName)
417GET_STRING_PROPERTY(backendComment)
418GET_STRING_PROPERTY(backendVersion)
419GET_STRING_PROPERTY(backendIcon)
420GET_STRING_PROPERTY(backendWebsite)
421#endif //QT_NO_PROPERTIES
422QObject *Factory::registerQObject(QObject *o)
423{
424 if (o) {
425 QObject::connect(o, SIGNAL(destroyed(QObject*)), globalFactory, SLOT(objectDestroyed(QObject*)), Qt::DirectConnection);
426 globalFactory->objects.append(o);
427 }
428 return o;
429}
430
431QList<BackendDescriptor> Factory::findBackends()
432{
433 static QList<Phonon::BackendDescriptor> backendList;
434 if (!backendList.isEmpty()) {
435 return backendList;
436 }
437 ensureLibraryPathSet();
438
439 QList<QString> iidPreference;
440 QSettings settings("kde.org", "libphonon");
441 const int size = settings.beginReadArray("Backends");
442 for (int i = 0; i < size; ++i) {
443 settings.setArrayIndex(i);
444 iidPreference.append(settings.value("iid").toString());
445 }
446 settings.endArray();
447
448 // Load default preference list.
450 for (const QString &path : paths) {
451 const QString libPath = path + PHONON_BACKEND_DIR_SUFFIX;
452 const QDir dir(libPath);
453 if (!dir.exists()) {
454 pDebug() << Q_FUNC_INFO << dir.absolutePath() << "does not exist";
455 continue;
456 }
457
458 const QStringList plugins(dir.entryList(QDir::Files));
459 for (const QString &plugin : plugins) {
460 Phonon::BackendDescriptor bd(libPath + plugin);
461 if (!bd.isValid) {
462 continue;
463 }
464
465 const auto index = iidPreference.indexOf(bd.iid);
466 if (index >= 0) {
467 // Apply a weight. Weight strongly influences sort order.
468 bd.weight = iidPreference.size() - index;
469 }
470 backendList.append(bd);
471 }
472
473 std::sort(backendList.rbegin(), backendList.rend());
474 }
475
476 // Apply PHONON_BACKEND override if set.
477 const QString backendEnv = qEnvironmentVariable("PHONON_BACKEND");
478 if (backendEnv.isEmpty()) {
479 return backendList;
480 }
481
482 for (int i = 0; i < backendList.size(); ++i) {
483 const auto &backend = backendList.at(i);
484 if (backendEnv == backend.pluginName) {
485 backendList.move(i, 0);
486 break;
487 }
488 }
489
490 return backendList;
491}
492
493} //namespace Phonon
494
495#include "factory.moc"
496#include "moc_factory_p.cpp"
497
498// vim: sw=4 ts=4
Type type(const QSqlDatabase &db)
QString path(const QString &relativePath)
KIOCORE_EXPORT QString dir(const QString &fileClass)
bool isEmpty() const const
void addLibraryPath(const QString &path)
QCoreApplication * instance()
QStringList libraryPaths()
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
qsizetype count() const const
qsizetype indexOf(const AT &value, qsizetype from) const const
bool isEmpty() const const
void move(qsizetype from, qsizetype to)
reverse_iterator rbegin()
reverse_iterator rend()
qsizetype size() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QString fromLocal8Bit(QByteArrayView str)
bool isEmpty() const const
DirectConnection
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:20:24 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.