Change copyrights from Nokia to Digia
[qt:qtfeedback.git] / src / feedback / qfeedbackplugin.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/legal
5 **
6 ** This file is part of the QtFeedback module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and Digia.  For licensing terms and
14 ** conditions see http://qt.digia.com/licensing.  For further information
15 ** use the contact form at http://qt.digia.com/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file.  Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Digia gives you certain additional
26 ** rights.  These rights are described in the Digia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** GNU General Public License Usage
30 ** Alternatively, this file may be used under the terms of the GNU
31 ** General Public License version 3.0 as published by the Free Software
32 ** Foundation and appearing in the file LICENSE.GPL included in the
33 ** packaging of this file.  Please review the following information to
34 ** ensure the GNU General Public License version 3.0 requirements will be
35 ** met: http://www.gnu.org/copyleft/gpl.html.
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qfeedbackplugininterfaces.h"
43 #include "qfeedbackplugin_p.h"
44 #include "qfeedbackeffect_p.h"
45
46 #include "qfeedbackpluginsearch.h"
47
48 #include <QtCore/QCoreApplication>
49 #include <QtCore/QStringList>
50 #include <QtCore/QDir>
51 #include <QtCore/QPluginLoader>
52 #include <QtCore/QHash>
53 #include <QDebug>
54
55 QT_BEGIN_NAMESPACE
56
57 /*!
58     \class QFeedbackInterface
59     \ingroup feedback
60     \inmodule QtFeedback
61
62     \brief The QFeedbackInterface class is the base class for plugins providing feedback.
63
64     This interface gives the possibility to report errors from within a backend plugin.
65 */
66
67 /*!
68     \fn QFeedbackInterface::reportError(const QFeedbackEffect *effect, QFeedbackEffect::ErrorType error)
69
70     Allows a plugin to report the specified \a error whenever necessary.  Errors most likely can happen
71     trying to play or pause an effect, which should be supplied as the parameter \a effect.
72 */
73
74 /*!
75     \enum QFeedbackInterface::PluginPriority
76
77     This enum describes the priority that the plugin should have in case more than one of the same type (Haptics or Theme) is found.
78     If more than one plugin has the same priority, the first one that has been loaded will be used.  However, multiple
79     file effect plugins can be loaded at the same time.
80
81     \value PluginLowPriority The plugin will have a low priority. This is usually the case for
82     platform specific-APIs.
83
84     \value PluginNormalPriority The plugin will have a normal priority.
85     This is usually the case for advanced technologies.
86
87     \value PluginHighPriority The plugin will have higher priority. Use this priority if you
88     want your own plugin to be used.
89 */
90
91
92 void QFeedbackInterface::reportError(const QFeedbackEffect *effect, QFeedbackEffect::ErrorType error)
93 {
94     if (effect)
95         emit effect->error(error);
96 }
97
98
99 // These are really useless docs, so I've marked them as internal
100 /*!
101   \internal
102   \fn QFeedbackThemeInterface::~QFeedbackThemeInterface()
103
104   Destroys any resources used by this interface.
105 */
106
107 /*!
108   \internal
109   \fn QFeedbackFileInterface::~QFeedbackFileInterface()
110
111   Destroys any resources used by this interface.
112 */
113
114 /*!
115   \internal
116   \fn QFeedbackHapticsInterface::~QFeedbackHapticsInterface()
117
118   Destroys any resources used by this interface.
119 */
120
121
122
123 template <class T>
124 class BackendLoader
125 {
126 public:
127     BackendLoader() : inst(0) { }
128     ~BackendLoader() { pl.unload(); }
129
130     void setInstance(T *newInst) { inst = newInst; }
131     T * instance() { return inst; }
132
133     void tryLoad(QPluginLoader &loader)
134     {
135         if (T *newInst = qobject_cast<T*>(loader.instance())) {
136             if (!inst || inst->pluginPriority() < newInst->pluginPriority()) {
137                 inst = newInst;
138                 pl.unload(); //release any reference to a previous plugin instance
139                 pl.setFileName(loader.fileName());
140                 pl.load(); //Adds a ref to the library
141             }
142         }
143     }
144
145
146 private:
147     QPluginLoader pl;
148     T *inst;
149 };
150
151
152 class FileBackend : public QFeedbackFileInterface
153 {
154 public:
155     FileBackend()
156     {
157     }
158
159     //this class is used to redirect the calls to all the file backends available
160     virtual void setLoaded(QFeedbackFileEffect *effect, bool load)
161     {
162         if (load) {
163             //start loading
164             tryBackendLoad(effect);
165         } else {
166             //unload
167             if (QFeedbackFileInterface *subBackend = getBackend(effect))
168                 subBackend->setLoaded(effect, load);
169             QFeedbackFileEffectPrivate::get(effect)->loadFinished(false); // make sure it's marked unloaded [XXX this isn't allowed to fail!]
170         }
171     }
172
173     virtual void setEffectState(QFeedbackFileEffect *effect, QFeedbackEffect::State state)
174     {
175         if (QFeedbackFileInterface *subBackend = getBackend(effect))
176             subBackend->setEffectState(effect, state);
177         else
178             QFeedbackInterface::reportError(effect, QFeedbackEffect::UnknownError);
179     }
180
181     virtual QFeedbackEffect::State effectState(const QFeedbackFileEffect *effect)
182     {
183         if (QFeedbackFileInterface *subBackend = getBackend(effect))
184             return subBackend->effectState(effect);
185
186         return QFeedbackEffect::Stopped;
187     }
188
189     virtual int effectDuration(const QFeedbackFileEffect *effect)
190     {
191          if (QFeedbackFileInterface *subBackend = getBackend(effect))
192             return subBackend->effectDuration(effect);
193
194         return 0;
195    }
196
197     virtual QStringList supportedMimeTypes()
198     {
199         QStringList ret;
200         for (int i = 0; i < subBackends.count(); ++i) {
201             ret += subBackends.at(i)->supportedMimeTypes();
202         }
203         return ret;
204     }
205
206     void addFileBackend(QFeedbackFileInterface *backend)
207     {
208         subBackends.append(backend);
209     }
210
211     void reportLoadFinished(QFeedbackFileEffect *effect, bool success)
212     {
213         if (success) {
214             //the file was loaded by the current backend
215             QFeedbackFileEffectPrivate::get(effect)->loadFinished(true);
216             return;
217         }
218
219         //let's try the next backend
220         tryBackendLoad(effect);
221     }
222
223 private:
224     QList<QFeedbackFileInterface*> subBackends;
225
226     QFeedbackFileInterface *getBackend(const QFeedbackFileEffect *effect)
227     {
228         const QFeedbackFileEffectPrivate *priv = QFeedbackFileEffectPrivate::get(effect);
229         if (priv->backendUsed >= 0 && priv->backendUsed < subBackends.count())
230                 return subBackends.at(priv->backendUsed);
231         return 0;
232     }
233
234     void tryBackendLoad(QFeedbackFileEffect *effect)
235     {
236         QFeedbackFileEffectPrivate *p = QFeedbackFileEffectPrivate::get(effect);
237         p->backendUsed++;
238
239         //let's try to load the file
240         if (p->backendUsed >= subBackends.count()) {
241             //the file couldn't be loaded
242             p->loadFinished(false);
243             reportError(effect, QFeedbackEffect::UnknownError);
244             // Do a state change as well, (to stopped)
245             QMetaObject::invokeMethod(effect, "stateChanged");
246             return;
247         }
248
249         subBackends.at(p->backendUsed)->setLoaded(effect, true);
250         //now we're waiting for the reply (call to asyncLoadFinished)
251     }
252 };
253
254
255 class BackendManager
256 {
257 public:
258     BackendManager()
259     {
260         QStringList pluginPaths = getPluginPaths(QLatin1String("feedback"));
261
262         foreach (const QString& pluginPath, pluginPaths) {
263             QPluginLoader loader(pluginPath);
264
265             hapticsBackend.tryLoad(loader);
266             themeBackend.tryLoad(loader);
267
268             if (QFeedbackFileInterface *newFile = qobject_cast<QFeedbackFileInterface*>(loader.instance())) {
269                 fileBackend.addFileBackend(newFile);
270             } else {
271                 loader.unload();
272             }
273         }
274
275         if (!hapticsBackend.instance())
276             hapticsBackend.setInstance(new QDummyBackend);
277     }
278
279     QFeedbackHapticsInterface* hapticsBackendInstance()
280     {
281         return hapticsBackend.instance();
282     }
283
284     QFeedbackThemeInterface* themeBackendInstance()
285     {
286         return themeBackend.instance();
287     }
288
289     FileBackend *fileBackendInstance()
290     {
291         return &fileBackend;
292     }
293
294 private:
295     BackendLoader<QFeedbackHapticsInterface> hapticsBackend;
296     BackendLoader<QFeedbackThemeInterface> themeBackend;
297     FileBackend fileBackend;
298 };
299
300 Q_GLOBAL_STATIC(BackendManager, backendManager)
301
302 /*!
303     \class QFeedbackHapticsInterface
304     \ingroup feedback
305
306     \brief The QFeedbackHapticsInterface class is the base class for plugins providing custom haptics effects.
307
308     This interface will be used to try to play custom effects with specific duration, intensity, envelope and period.
309     An effect is always played on a specified actuator.
310 */
311
312
313 /*!
314    \enum QFeedbackHapticsInterface::EffectProperty
315    This enum describes all effect properties for haptics effects.
316
317    \value Duration The effect duration (in milliseconds)
318    \value Intensity The effect intensity
319    \value AttackTime The effect attack time (in milliseconds)
320    \value AttackIntensity The effect attack intensity
321    \value FadeTime The effect fade time (in milliseconds)
322    \value FadeIntensity The effect fade intensity
323    \value Period The effect period, this is an optional effect property.
324 */
325
326 /*!
327   \enum QFeedbackHapticsInterface::ActuatorProperty
328   This enum describes all actuator properties.
329
330   \value Name The actuator name.
331   \value State The actuator state.
332   \value Enabled The actuator enabled state.
333  */
334
335
336 /*!
337     \fn QFeedbackHapticsInterface::actuators()
338
339     Return the available actuators provided by this plugin. The ownership of the actuator objects stays with the plugin.
340 */
341
342 /*!
343     \fn QFeedbackHapticsInterface::pluginPriority()
344
345     Returns the priority for the plugin.
346     \sa QFeedbackInterface::PluginPriority
347 */
348
349
350 // XXX TODO.. these should have been pointers to QFA :/
351 /*!
352     \fn QFeedbackHapticsInterface::setActuatorProperty(const QFeedbackActuator& actuator, ActuatorProperty property, const QVariant & value)
353
354     Sets a \a value for \a property on the \a actuator.
355
356     \sa ActuatorProperty
357 */
358
359 /*!
360     \fn QFeedbackHapticsInterface::actuatorProperty(const QFeedbackActuator & actuator, ActuatorProperty property)
361
362     Returns the value for the \a property for an \a actuator.
363
364     \sa ActuatorProperty
365 */
366
367 /*!
368     \fn QFeedbackHapticsInterface::isActuatorCapabilitySupported(const QFeedbackActuator &actuator, QFeedbackActuator::Capability capability)
369
370     Returns true if the \a actuator supports the \a capability.
371 */
372
373
374 /*!
375     \fn QFeedbackHapticsInterface::updateEffectProperty(const QFeedbackHapticsEffect *effect, EffectProperty property)
376
377     Tells the backend that the \a property has been updated for the supplied \a effect.
378 */
379
380 /*!
381     \fn QFeedbackHapticsInterface::setEffectState(const QFeedbackHapticsEffect *effect, QFeedbackEffect::State state)
382
383     Sets the state to \a state for the effect \a effect. If that fails the backend should report an error by
384     calling reportError and \a effect will in turn emit an error signal.
385 */
386
387 /*!
388     \fn QFeedbackHapticsInterface::effectState(const QFeedbackHapticsEffect *effect)
389
390     Get the current state for the effect \a effect.
391 */
392
393 /*!
394     \internal
395     \fn QFeedbackHapticsInterface::instance()
396
397     Returns the instance of the object managing haptics custom effects.
398     If no backend has been loaded, this will return a null pointer.
399 */
400 QFeedbackHapticsInterface *QFeedbackHapticsInterface::instance()
401 {
402     return backendManager()->hapticsBackendInstance();
403 }
404
405 /*!
406     \fn QFeedbackHapticsInterface::createFeedbackActuator(QObject *parent, int id)
407
408     Creates an instance of QFeedbackActuator with the identifier \a id and parent \a parent.  This allows
409     backends to create instances of actuators. It is then up to the each backend to manage
410     the identifiers according to its needs.
411 */
412 QFeedbackActuator* QFeedbackHapticsInterface::createFeedbackActuator(QObject* parent, int id)
413 {
414     return new QFeedbackActuator(parent, id);
415 }
416
417 /*!
418     \class QFeedbackThemeInterface
419     \ingroup feedback
420
421     \brief The QFeedbackThemeInterface class is the base class for plugins providing themed effects.
422
423     They can be of any nature (tactile, audio...).
424     This simple interface will be used to play those themed effects by a simple call to the play method.
425 */
426
427
428 /*!
429     \fn QFeedbackThemeInterface::pluginPriority()
430
431     Returns the priority for the plugin.
432 */
433
434 /*!
435     \fn QFeedbackThemeInterface::play(QFeedbackEffect::Effect effect)
436
437     Plays the theme effect \a effect. Returns false in case of an error.
438 */
439
440 /*!
441     \internal
442     \fn QFeedbackThemeInterface::instance()
443
444     Returns the instance of the object managing theme effects.
445     If no backend has been loaded, this will return a null pointer.
446 */
447 QFeedbackThemeInterface *QFeedbackThemeInterface::instance()
448 {
449     return backendManager()->themeBackendInstance();
450 }
451
452 /*!
453     \internal
454     \class QFeedbackFileInterface
455     \ingroup feedback
456
457     \brief The QFeedbackFileInterface class is the base class for plugins providing support for effects stored in files.
458
459     They can be of any nature (tactile, audio...). As it is possible to load many different file types using
460     different technologies, all the backend plugins exposing this interface will be loaded at the same time.
461     When loading a file all the backend will be tried in order until one can load the file. It is thus very important
462     that the backends return a load status as soon as possible to not take a too long time to load a file.
463 */
464
465 /*!
466     \internal
467     \fn QFeedbackFileInterface::setLoaded(QFeedbackFileEffect* effect, bool value)
468
469     Sets the state of the effect \a effect to be loaded if \a value is true, otherwise unloaded.
470     Loading a file is asynchronous. Once the backend knows if it has loaded or can't load the file, it must
471     call the reportLoadFinished function.
472 */
473
474 /*!
475     \internal
476     \fn QFeedbackFileInterface::setEffectState(QFeedbackFileEffect *effect, QFeedbackEffect::State state)
477
478     Sets the state of \a effect to \a state.
479 */
480
481 /*!
482     \internal
483     \fn QFeedbackFileInterface::effectState(const QFeedbackFileEffect *effect)
484
485     Returns the current state of the effect \a effect.
486 */
487
488 /*!
489     \internal
490     \fn QFeedbackFileInterface::effectDuration(const QFeedbackFileEffect *effect)
491
492     Return the duration of \a effect, in milliseconds.
493     It should return \l QFeedbackEffect::Infinite in case the duration is infinite, or 0 if undefined or unknown.
494 */
495
496 /*!
497     \internal
498     \fn QFeedbackFileInterface::supportedMimeTypes()
499
500     Returns a list of the MIME types supported by this plugin.
501 */
502
503 /*!
504     \internal
505     \fn QFeedbackFileInterface::instance()
506
507     Returns the instance of the object managing theme effects.
508     Even if no backend has been loaded, this will never return a null pointer.
509 */
510 QFeedbackFileInterface *QFeedbackFileInterface::instance()
511 {
512     return backendManager()->fileBackendInstance();
513 }
514
515 /*!
516     \internal
517     \fn QFeedbackFileInterface::reportLoadFinished(QFeedbackFileEffect *effect, bool success)
518
519     This is the function the backend should call when it has finished trying to load the effect \a effect.
520     As loading a file is asynchronous and multiple plugins are attempted after each other, the
521     backend has to call this function in order for the process to perform smoothly.
522     The success of the operation is indicated by the \a success parameter.
523 */
524 void QFeedbackFileInterface::reportLoadFinished(QFeedbackFileEffect *effect, bool success)
525 {
526     backendManager()->fileBackendInstance()->reportLoadFinished(effect, success);
527 }
528
529 QT_END_NAMESPACE