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 | |
45 | namespace Phonon |
46 | { |
47 | |
48 | class PlatformPlugin; |
49 | class 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 | |
78 | PHONON_GLOBAL_STATIC(Phonon::FactoryPrivate, globalFactory) |
79 | |
80 | static inline void ensureLibraryPathSet() |
81 | { |
82 | #ifdef PHONON_PLUGIN_PATH |
83 | static bool done = false; |
84 | if (!done) { |
85 | done = true; |
86 | QCoreApplication::addLibraryPath(QLatin1String(PHONON_PLUGIN_PATH)); |
87 | } |
88 | #endif // PHONON_PLUGIN_PATH |
89 | } |
90 | |
91 | void Factory::setBackend(QObject *b) |
92 | { |
93 | Q_ASSERT(globalFactory->m_backendObject == nullptr); |
94 | globalFactory->m_backendObject = b; |
95 | } |
96 | |
97 | bool 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. |
118 | bool 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(varName: "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(path: 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(name: "backendName" ).toString() |
156 | << "version" |
157 | << m_backendObject->property(name: "backendVersion" ).toString() |
158 | << "loaded" ; |
159 | |
160 | connect(asender: 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 | |
171 | FactoryPrivate::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 | |
185 | FactoryPrivate::~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(c: objects); |
193 | } |
194 | delete m_backendObject; |
195 | #ifndef QT_NO_PHONON_PLATFORMPLUGIN |
196 | delete m_platformPlugin; |
197 | #endif //QT_NO_PHONON_PLATFORMPLUGIN |
198 | } |
199 | |
200 | void 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 | |
223 | Factory::Sender *Factory::sender() |
224 | { |
225 | return globalFactory; |
226 | } |
227 | |
228 | bool 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 | |
241 | void Factory::registerFrontendObject(MediaNodePrivate *bp) |
242 | { |
243 | globalFactory->mediaNodePrivateList.prepend(t: bp); // inserted last => deleted first |
244 | } |
245 | |
246 | void 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(t: 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 | |
262 | void FactoryPrivate::objectDestroyed(QObject * obj) |
263 | { |
264 | //pDebug() << Q_FUNC_INFO << obj; |
265 | objects.removeAll(t: obj); |
266 | } |
267 | |
268 | #define FACTORY_IMPL(classname) \ |
269 | QObject *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) \ |
277 | QObject *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 | |
285 | FACTORY_IMPL(MediaObject) |
286 | #ifndef QT_NO_PHONON_EFFECT |
287 | FACTORY_IMPL_1ARG(Effect) |
288 | #endif //QT_NO_PHONON_EFFECT |
289 | #ifndef QT_NO_PHONON_VOLUMEFADEREFFECT |
290 | FACTORY_IMPL(VolumeFaderEffect) |
291 | #endif //QT_NO_PHONON_VOLUMEFADEREFFECT |
292 | FACTORY_IMPL(AudioOutput) |
293 | #ifndef QT_NO_PHONON_VIDEO |
294 | FACTORY_IMPL(VideoWidget) |
295 | // TODO P6: remove left overs from VGO. was removed except for factory references. |
296 | FACTORY_IMPL(VideoGraphicsObject) |
297 | #endif //QT_NO_PHONON_VIDEO |
298 | FACTORY_IMPL(AudioDataOutput) |
299 | |
300 | #undef FACTORY_IMPL |
301 | |
302 | #ifndef QT_NO_PHONON_PLATFORMPLUGIN |
303 | PlatformPlugin *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(varName: "PHONON_PLATFORMPLUGIN" ); |
313 | if (!platform_plugin_env.isEmpty()) { |
314 | pDebug() << Q_FUNC_INFO << "platform plugin path:" << platform_plugin_env; |
315 | QPluginLoader pluginLoader(QString::fromLocal8Bit(ba: 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 *>(object: 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(varName: "KDE_FULL_SESSION" ).isEmpty() ? QStringList(QLatin1String("kde.*" )) : |
336 | (!qgetenv(varName: "GNOME_DESKTOP_SESSION_ID" ).isEmpty() ? QStringList(QLatin1String("gnome.*" )) : |
337 | QStringList()) |
338 | ); |
339 | dir.setFilter(QDir::Files); |
340 | const QStringList libPaths = QCoreApplication::libraryPaths(); |
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(filters: 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 *>(object: qobj); |
360 | pDebug() << m_platformPlugin; |
361 | if (m_platformPlugin) { |
362 | connect(asender: 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 | |
382 | PlatformPlugin *Factory::platformPlugin() |
383 | { |
384 | return globalFactory->platformPlugin(); |
385 | } |
386 | #endif // QT_NO_PHONON_PLATFORMPLUGIN |
387 | |
388 | QObject *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) \ |
407 | QString Factory::name() \ |
408 | { \ |
409 | if (globalFactory->m_backendObject) { \ |
410 | return globalFactory->m_backendObject->property(#name).toString(); \ |
411 | } \ |
412 | return QString(); \ |
413 | } \ |
414 | |
415 | GET_STRING_PROPERTY(identifier) |
416 | GET_STRING_PROPERTY(backendName) |
417 | GET_STRING_PROPERTY() |
418 | GET_STRING_PROPERTY(backendVersion) |
419 | GET_STRING_PROPERTY(backendIcon) |
420 | GET_STRING_PROPERTY(backendWebsite) |
421 | #endif //QT_NO_PROPERTIES |
422 | QObject *Factory::registerQObject(QObject *o) |
423 | { |
424 | if (o) { |
425 | QObject::connect(sender: o, SIGNAL(destroyed(QObject*)), receiver: globalFactory, SLOT(objectDestroyed(QObject*)), Qt::DirectConnection); |
426 | globalFactory->objects.append(t: o); |
427 | } |
428 | return o; |
429 | } |
430 | |
431 | QList<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(prefix: "Backends" ); |
442 | for (int i = 0; i < size; ++i) { |
443 | settings.setArrayIndex(i); |
444 | iidPreference.append(t: settings.value(key: "iid" ).toString()); |
445 | } |
446 | settings.endArray(); |
447 | |
448 | // Load default preference list. |
449 | const QStringList paths = QCoreApplication::libraryPaths(); |
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(filters: 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(str: 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(t: bd); |
471 | } |
472 | |
473 | std::sort(first: backendList.rbegin(), last: backendList.rend()); |
474 | } |
475 | |
476 | // Apply PHONON_BACKEND override if set. |
477 | const QString backendEnv = qEnvironmentVariable(varName: "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(from: i, to: 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 |
499 | |