Change copyrights from Nokia to Digia
[qt:qtfeedback.git] / src / plugins / feedback / immersion / qfeedback.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 <qfeedbackactuator.h>
43 #include "qfeedback.h"
44 #include <QtCore/QtPlugin>
45 #include <QtCore/QDebug>
46 #include <QtCore/QStringList>
47 #include <QtCore/QCoreApplication>
48 #include <QtCore/QFile>
49 #include <QtCore/QVariant>
50 #include <QtCore/QTimer>
51 #include <QDebug>
52
53 #define MAX_FILE_SIZE (1 << 14) //16KB
54
55 QFeedbackImmersion::QFeedbackImmersion() : QObject(qApp)
56 {
57     if (VIBE_FAILED(ImmVibeInitialize(VIBE_CURRENT_VERSION_NUMBER))) {
58         //that should be done once
59         //error management
60         qWarning() << "the Immersion library could not be initialized";
61     } else {
62         const int nbDev = ImmVibeGetDeviceCount();
63         for (int i = 0; i < nbDev; ++i) {
64             actuatorList << createFeedbackActuator(this, i);
65         }
66     }
67 }
68
69 QFeedbackImmersion::~QFeedbackImmersion()
70 {
71     //cleanup the devices when terminating
72     for (int i = 0 ; i < actuatorHandles.size(); ++i)
73         ImmVibeCloseDevice(actuatorHandles.at(i));
74
75     ImmVibeTerminate();
76 }
77
78 QFeedbackInterface::PluginPriority QFeedbackImmersion::pluginPriority()
79 {
80     return PluginNormalPriority;
81 }
82
83 QList<QFeedbackActuator*> QFeedbackImmersion::actuators()
84 {
85     return actuatorList;
86 }
87
88 void QFeedbackImmersion::setActuatorProperty(const QFeedbackActuator &actuator, ActuatorProperty prop, const QVariant &value)
89 {
90     switch (prop)
91     {
92     case Enabled:
93         ImmVibeSetDevicePropertyBool(handleForActuator(actuator), VIBE_DEVPROPTYPE_DISABLE_EFFECTS, !value.toBool());
94         break;
95     default:
96         break;
97     }
98 }
99
100 QVariant QFeedbackImmersion::actuatorProperty(const QFeedbackActuator &actuator, ActuatorProperty prop)
101 {
102     switch (prop)
103     {
104     case Name:
105         {
106             char szDeviceName[VIBE_MAX_DEVICE_NAME_LENGTH] = { 0 };
107             if (VIBE_FAILED(ImmVibeGetDeviceCapabilityString(actuator.id(), VIBE_DEVCAPTYPE_DEVICE_NAME,
108                 VIBE_MAX_CAPABILITY_STRING_LENGTH, szDeviceName)))
109                 return QString();
110
111             return QString::fromLocal8Bit(szDeviceName);
112         }
113
114     case State:
115         {
116             QFeedbackActuator::State ret = QFeedbackActuator::Unknown;
117             VibeInt32 s = 0;
118             if (actuator.isValid() && VIBE_SUCCEEDED(ImmVibeGetDeviceState(actuator.id(), &s))) {
119                 if (s == VIBE_DEVICESTATE_ATTACHED)
120                     ret = QFeedbackActuator::Ready;
121                 else if (s == VIBE_DEVICESTATE_BUSY)
122                     ret = QFeedbackActuator:: Busy;
123             }
124
125             return ret;
126         }
127     case Enabled:
128         {
129             VibeBool disabled = true;
130             if (VIBE_FAILED(ImmVibeGetDevicePropertyBool(handleForActuator(actuator), VIBE_DEVPROPTYPE_DISABLE_EFFECTS, &disabled)))
131                 return false;
132             return !disabled;
133         }
134     default:
135         return QVariant();
136     }
137 }
138
139 bool QFeedbackImmersion::isActuatorCapabilitySupported(const QFeedbackActuator &, QFeedbackActuator::Capability cap)
140 {
141     switch(cap)
142     {
143     case QFeedbackActuator::Envelope:
144     case QFeedbackActuator::Period:
145         return true;
146     default:
147         return false;
148     }
149 }
150
151
152 VibeInt32 QFeedbackImmersion::convertedDuration(int duration)
153 {
154     //this allows to manage the infinite durations
155     if (duration == -1)
156         return VIBE_TIME_INFINITE;
157     return duration;
158 }
159
160
161 VibeInt32 QFeedbackImmersion::handleForActuator(const QFeedbackActuator &actuator)
162 {
163     return handleForActuator(actuator.id());
164 }
165
166 VibeInt32 QFeedbackImmersion::handleForActuator(int actId)
167 {
168     if (actId < 0)
169         return VIBE_INVALID_DEVICE_HANDLE_VALUE;
170
171     //we avoid locking too much (it will only lock if the device is not yet open
172     if (actuatorHandles.size() <= actId) {
173         QMutexLocker locker(&mutex);
174         while (actuatorHandles.size() <= actId)
175             actuatorHandles.append(VIBE_INVALID_DEVICE_HANDLE_VALUE);
176     }
177
178     if (VIBE_IS_INVALID_DEVICE_HANDLE(actuatorHandles.at(actId))) {
179         QMutexLocker locker(&mutex);
180         if (VIBE_IS_INVALID_DEVICE_HANDLE(actuatorHandles.at(actId))) {
181             ImmVibeOpenDevice(actId, &actuatorHandles[actId] );
182
183             //temporary solution: provide a proto dev license key
184             ImmVibeSetDevicePropertyString(actuatorHandles.at(actId), VIBE_DEVPROPTYPE_LICENSE_KEY, "IMWPROTOSJZF4EH6KWVUK8HAP5WACT6Q");
185         }
186     }
187     return actuatorHandles.at(actId);
188 }
189
190 void QFeedbackImmersion::updateEffectProperty(const QFeedbackHapticsEffect *effect, EffectProperty)
191 {
192     VibeInt32 effectHandle = effectHandles.value(effect, VIBE_INVALID_EFFECT_HANDLE_VALUE);
193     if (VIBE_IS_INVALID_EFFECT_HANDLE(effectHandle))
194         return; // the effect is simply not running
195
196     VibeStatus status = VIBE_S_SUCCESS;
197     if (effect->period() > 0) {
198         status = ImmVibeModifyPlayingPeriodicEffect(handleForActuator(effect->actuator()), effectHandle,
199                                            convertedDuration(effect->duration()),
200                                            effect->intensity() * qreal(VIBE_MAX_MAGNITUDE), effect->period(),
201                                            VIBE_DEFAULT_STYLE,
202                                            effect->attackTime(), effect->attackIntensity() * qreal(VIBE_MAX_MAGNITUDE),
203                                            effect->fadeTime(), effect->fadeIntensity() * qreal(VIBE_MAX_MAGNITUDE));
204     } else {
205         status = ImmVibeModifyPlayingMagSweepEffect(handleForActuator(effect->actuator()), effectHandle,
206                                            convertedDuration(effect->duration()),
207                                            effect->intensity() * qreal(VIBE_MAX_MAGNITUDE),
208                                            VIBE_DEFAULT_STYLE,
209                                            effect->attackTime(), effect->attackIntensity() * qreal(VIBE_MAX_MAGNITUDE),
210                                            effect->fadeTime(), effect->fadeIntensity() * qreal(VIBE_MAX_MAGNITUDE));
211     }
212
213     // Hmm. Not sure if the duration needs to be updated or not
214     if (VIBE_SUCCEEDED(status)) {
215         startTimerForHandle(effectHandle, effect); // this will kill old one, if necessary
216     }
217
218     if (VIBE_FAILED(status))
219         reportError(effect, QFeedbackEffect::UnknownError);
220
221 }
222
223 void QFeedbackImmersion::killTimerForHandle(VibeInt32 handle)
224 {
225     QTimer* t = effectTimers.take(handle);
226     if (t) {
227         t->stop();
228         delete t;
229     }
230 }
231
232 void QFeedbackImmersion::startTimerForHandle(VibeInt32 handle, const QFeedbackHapticsEffect *effect)
233 {
234     // Make sure we don't have one already
235     killTimerForHandle(handle);
236
237     // And create a timer for a non infinite, non periodic effect
238     if (effect->period() <= 0 && effect->duration() > 0) {
239         QTimer* t = new QTimer();
240         t->setSingleShot(true);
241         t->setInterval(effect->duration() + effect->attackTime() + effect->fadeTime());
242         connect(t, SIGNAL(timeout()), const_cast<QFeedbackHapticsEffect*>(effect), SIGNAL(stateChanged()));
243         effectTimers.insert(handle, t);
244         t->start();
245     }
246 }
247
248 void QFeedbackImmersion::startTimerForHandle(VibeInt32 handle, QFeedbackFileEffect *effect)
249 {
250     // Make sure we don't have one already
251     killTimerForHandle(handle);
252
253     // And create a timer for a non infinite effect
254     if (effect->duration() > 0) {
255         QTimer* t = new QTimer();
256         t->setSingleShot(true);
257         t->setInterval(effect->duration());
258         connect(t, SIGNAL(timeout()), effect, SIGNAL(stateChanged()));
259         effectTimers.insert(handle, t);
260         t->start();
261     }
262 }
263
264 void QFeedbackImmersion::setEffectState(const QFeedbackHapticsEffect *effect, QFeedbackEffect::State state)
265 {
266     VibeStatus status = VIBE_S_SUCCESS;
267     VibeInt32 effectHandle = effectHandles.value(effect, VIBE_INVALID_EFFECT_HANDLE_VALUE);
268
269     switch (state)
270     {
271     case QFeedbackEffect::Stopped:
272         if (VIBE_IS_VALID_EFFECT_HANDLE(effectHandle)) {
273             status = ImmVibeStopPlayingEffect(handleForActuator(effect->actuator()), effectHandle);
274             effectHandles.remove(effect);
275             killTimerForHandle(effectHandle);
276         }
277         break;
278     case QFeedbackEffect::Paused:
279         Q_ASSERT(VIBE_IS_VALID_EFFECT_HANDLE(effectHandle));
280         status = ImmVibePausePlayingEffect(handleForActuator(effect->actuator()), effectHandle);
281         killTimerForHandle(effectHandle);
282         break;
283     case QFeedbackEffect::Running:
284         //if the effect handle exists, the feedback must be paused 
285         if (VIBE_IS_VALID_EFFECT_HANDLE(effectHandle)) {
286             status = ImmVibeResumePausedEffect(handleForActuator(effect->actuator()), effectHandle);
287             // Restart a timer if needed (only aperiodic ones)
288             if (VIBE_SUCCEEDED(status)) {
289                 startTimerForHandle(effectHandle, effect);
290             }
291         } else {
292             //we need to start the effect and create the handle
293             VibeInt32 effectHandle = VIBE_INVALID_EFFECT_HANDLE_VALUE;
294             if (effect->period() > 0) {
295                 status = ImmVibePlayPeriodicEffect(handleForActuator(effect->actuator()), convertedDuration(effect->duration()),
296                     qRound(effect->intensity() * qreal(VIBE_MAX_MAGNITUDE)), effect->period(),
297                     VIBE_DEFAULT_STYLE, effect->attackTime(), effect->attackIntensity() * qreal(VIBE_MAX_MAGNITUDE),
298                     effect->fadeTime(), effect->fadeIntensity() * qreal(VIBE_MAX_MAGNITUDE), &effectHandle);
299             } else {
300                 status = ImmVibePlayMagSweepEffect(handleForActuator(effect->actuator()), convertedDuration(effect->duration()),
301                     qRound(effect->intensity() * qreal(VIBE_MAX_MAGNITUDE)),
302                     VIBE_DEFAULT_STYLE, effect->attackTime(), effect->attackIntensity() * qreal(VIBE_MAX_MAGNITUDE),
303                     effect->fadeTime(), effect->fadeIntensity() * qreal(VIBE_MAX_MAGNITUDE), &effectHandle);
304             }
305             if (VIBE_SUCCEEDED(status)) {
306                 effectHandles.insert(effect, effectHandle);
307                 startTimerForHandle(effectHandle, effect);
308             }
309         }
310         break;
311     default:
312         break;
313     }
314 }
315
316 QFeedbackEffect::State QFeedbackImmersion::effectState(const QFeedbackHapticsEffect *effect)
317 {
318     VibeInt32 effectHandle = effectHandles.value(effect, VIBE_INVALID_EFFECT_HANDLE_VALUE);
319     if (VIBE_IS_INVALID_EFFECT_HANDLE(effectHandle))
320         return QFeedbackEffect::Stopped; // the effect is simply not running
321
322     VibeInt32 effectState = VIBE_EFFECT_STATE_NOT_PLAYING;
323     ImmVibeGetEffectState(handleForActuator(effect->actuator()), effectHandle, &effectState);
324
325     return updateImmState(effect, effectHandle, effectState);
326 }
327
328 // **************************
329 // !!! File based stuff below
330 // **************************
331
332
333 void QFeedbackImmersion::setLoaded(QFeedbackFileEffect *effect, bool load)
334 {
335     const QUrl url = effect->source();
336
337     // This doesn't handle qrc urls..
338     const QString fileName = url.toLocalFile();
339     if (fileName.isEmpty())
340         return;
341
342     if (!load && !fileData.contains(fileName))
343         return;
344
345     FileContent &fc = fileData[fileName];
346     if (load) {
347         bool success = true;
348         if (fc.refCount == 0) {
349             //we need to load the file
350             QFile file(fileName);
351             success = false;
352             if (file.size() < MAX_FILE_SIZE && file.open(QIODevice::ReadOnly)) {
353                 fc.ba = file.readAll();
354                 //now let's try to check the file content with immersion
355                 if (VIBE_FAILED(ImmVibeGetIVTEffectCount(fc.constData()))) {
356                     fileData.remove(fileName); 
357                 } else {
358                     fc.refCount++;
359                     success = true;
360                 }
361             }
362         }
363         reportLoadFinished(effect, success);
364     } else {
365         //unload
366         if (fc.refCount > 0)
367             fc.refCount--;
368         if (fc.refCount == 0)
369             fileData.remove(fileName);
370     }
371 }
372
373 void QFeedbackImmersion::setEffectState(QFeedbackFileEffect *effect, QFeedbackEffect::State state)
374 {
375     VibeStatus status = VIBE_S_SUCCESS;
376     VibeInt32 effectHandle = effectHandles.value(effect, VIBE_INVALID_EFFECT_HANDLE_VALUE);
377
378     VibeInt32 dev = handleForActuator(0); //we always use the default (first) device
379
380     switch (state)
381     {
382     case QFeedbackEffect::Stopped:
383         if (VIBE_IS_VALID_EFFECT_HANDLE(effectHandle)) {
384             status = ImmVibeStopPlayingEffect(dev, effectHandle);
385             effectHandles.remove(effect);
386             killTimerForHandle(effectHandle);
387         }
388         break;
389     case QFeedbackEffect::Paused:
390         Q_ASSERT(VIBE_IS_VALID_EFFECT_HANDLE(effectHandle));
391         status = ImmVibePausePlayingEffect(dev, effectHandle);
392         killTimerForHandle(effectHandle);
393         break;
394     case QFeedbackEffect::Running:
395         //if the effect handle exists, the feedback must be paused 
396         if (VIBE_IS_VALID_EFFECT_HANDLE(effectHandle)) {
397             status = ImmVibeResumePausedEffect(dev, effectHandle);
398             if (VIBE_SUCCEEDED(status)) {
399                 startTimerForHandle(effectHandle, effect);
400             }
401         } else {
402             //we need to start the effect and create the handle
403             QString fileName = effect->source().toLocalFile();
404             Q_ASSERT(fileData.contains(fileName));
405             status = ImmVibePlayIVTEffect(dev, fileData[fileName].constData(), 0, &effectHandle);
406             if (VIBE_SUCCEEDED(status)) {
407                 effectHandles.insert(effect, effectHandle);
408                 startTimerForHandle(effectHandle, effect);
409             }
410         }
411         break;
412     default:
413         break;
414     }
415
416     if (VIBE_FAILED(status))
417         reportError(effect, QFeedbackEffect::UnknownError);
418 }
419
420 QFeedbackEffect::State QFeedbackImmersion::updateImmState(const QFeedbackEffect *effect, VibeInt32 effectHandle, VibeInt32 effectState)
421 {
422     // here we detect changes in the state of the effect
423     // and make sure any timers are updated
424     switch(effectState)
425     {
426     case VIBE_EFFECT_STATE_PAUSED:
427         killTimerForHandle(effectHandle);
428         return QFeedbackEffect::Paused;
429     case VIBE_EFFECT_STATE_PLAYING: {
430         // If the timer existed and is not running, we probably need
431         // to reschedule it so the effect state can be up to date
432         QTimer *t = effectTimers.value(effectHandle);
433         if (t && !t->isActive()) {
434             // Well, perhaps the timer fired early.  Trigger it again later
435             t->setInterval(50); // XXX arbitrary choice
436             t->start();
437         }
438         return QFeedbackEffect::Running;
439     }
440     case VIBE_EFFECT_STATE_NOT_PLAYING:
441     default:
442         killTimerForHandle(effectHandle);
443         if (effectHandles.contains(effect)) {
444             effectHandles.remove(effect);
445             // because we kill the timer before it fires stateChanged, we need to emit stateChanged ourself.
446             QMetaObject::invokeMethod(const_cast<QFeedbackEffect*>(effect), "stateChanged");
447         }
448         return QFeedbackEffect::Stopped;
449     }
450 }
451
452 QFeedbackEffect::State QFeedbackImmersion::effectState(const QFeedbackFileEffect *effect)
453 {
454     VibeInt32 effectHandle = effectHandles.value(effect, VIBE_INVALID_EFFECT_HANDLE_VALUE);
455     if (VIBE_IS_INVALID_EFFECT_HANDLE(effectHandle))
456         return QFeedbackEffect::Stopped; // the effect is simply not running
457
458     VibeInt32 effectState = VIBE_EFFECT_STATE_NOT_PLAYING;
459     ImmVibeGetEffectState(handleForActuator(0), effectHandle, &effectState);
460
461     return updateImmState(effect, effectHandle, effectState);
462 }
463
464 int QFeedbackImmersion::effectDuration(const QFeedbackFileEffect *effect)
465 {
466     VibeInt32 ret = 0;
467     QString fileName = effect->source().toLocalFile();
468     if (fileData.contains(fileName))
469         ImmVibeGetIVTEffectDuration(fileData[fileName].constData(), 0, &ret);
470
471     return ret;
472 }
473
474 QStringList QFeedbackImmersion::supportedMimeTypes()
475 {
476     return QStringList() << QLatin1String("vibra/ivt");
477 }