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