From: Michal MalĂ˝ Date: Fri, 26 Jul 2013 07:28:13 +0000 (+0200) Subject: Add all files X-Git-Tag: 0.2a~7 X-Git-Url: https://gitweb.devoid-pointer.net/?a=commitdiff_plain;h=73f1129fa11c83a5f4ce86aabad7fac9e197f5bf;p=FFBChecker.git Add all files --- diff --git a/FFBChecker.pro b/FFBChecker.pro new file mode 100644 index 0000000..e533784 --- /dev/null +++ b/FFBChecker.pro @@ -0,0 +1,57 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2013-03-28T12:34:18 +# +#------------------------------------------------- + +QT += core gui + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +TARGET = FFBChecker +TEMPLATE = app + + +SOURCES += main.cpp\ + mainwindow.cpp \ + deviceprober.cpp \ + ffbdevice.cpp \ + ffbeffect.cpp \ + ffbeffectparameters.cpp \ + ffbconstanteffectparameters.cpp \ + effectsettings.cpp \ + constanteffectsettings.cpp \ + envelopesettings.cpp \ + periodiceffectsettings.cpp \ + ffbconstanteffect.cpp \ + ffbeffectfactory.cpp \ + ffbenvelopeparameters.cpp \ + ffbperiodiceffectparameters.cpp \ + ffbperiodiceffect.cpp \ + ffbnulleffect.cpp + +HEADERS += mainwindow.h \ + deviceprober.h \ + ffbdevice.h \ + helpers.h \ + ffbeffect.h \ + globals.h \ + ffbeffectparameters.h \ + ffbconstanteffectparameters.h \ + effectsettings.h \ + constanteffectsettings.h \ + envelopesettings.h \ + periodiceffectsettings.h \ + ffbconstanteffect.h \ + ffbeffectfactory.h \ + ffbenvelopeparameters.h \ + ffbperiodiceffectparameters.h \ + ffbperiodiceffect.h \ + ffbnulleffect.h + +FORMS += mainwindow.ui \ + constanteffectsettings.ui \ + envelopesettings.ui \ + periodiceffectsettings.ui + +QMAKE_CXXFLAGS += -std=c++11 -Wall diff --git a/constanteffectsettings.cpp b/constanteffectsettings.cpp new file mode 100644 index 0000000..555f87d --- /dev/null +++ b/constanteffectsettings.cpp @@ -0,0 +1,39 @@ +#include "constanteffectsettings.h" +#include "ui_constanteffectsettings.h" + +ConstantEffectSettings::ConstantEffectSettings(QWidget* parent) : + EffectSettings(parent), + ui(new Ui::ConstantEffectSettings) +{ + ui->setupUi(this); +} + +const EnvelopeSettings* ConstantEffectSettings::envelopeSettings() const +{ + return ui->qwid_envelope; +} + +QString ConstantEffectSettings::level() const +{ + return ui->qle_level->text(); +} + +bool ConstantEffectSettings::fillFromParameters(const std::shared_ptr params) +{ + try { + const std::shared_ptr cfParams = std::dynamic_pointer_cast(params); + + ui->qle_level->setText(QString::number(cfParams->level)); + return ui->qwid_envelope->fillFromParameters(params); + } catch (std::bad_cast& ex) { + qCritical(ex.what()); + return false; + } + return false; +} + + +ConstantEffectSettings::~ConstantEffectSettings() +{ + delete ui; +} diff --git a/constanteffectsettings.h b/constanteffectsettings.h new file mode 100644 index 0000000..a6a4670 --- /dev/null +++ b/constanteffectsettings.h @@ -0,0 +1,28 @@ +#ifndef CONSTANTEFFECTSETTINGS_H +#define CONSTANTEFFECTSETTINGS_H + +#include "effectsettings.h" +#include "envelopesettings.h" +#include "ffbconstanteffectparameters.h" + +namespace Ui { + class ConstantEffectSettings; +} + +class ConstantEffectSettings : public EffectSettings +{ + Q_OBJECT + +public: + explicit ConstantEffectSettings(QWidget* parent = 0); + ~ConstantEffectSettings(); + const EnvelopeSettings* envelopeSettings() const; + bool fillFromParameters(const std::shared_ptr params); + QString level() const; + +private: + Ui::ConstantEffectSettings* ui; + +}; + +#endif // CONSTANTEFFECTSETTINGS_H diff --git a/constanteffectsettings.ui b/constanteffectsettings.ui new file mode 100644 index 0000000..c105f96 --- /dev/null +++ b/constanteffectsettings.ui @@ -0,0 +1,99 @@ + + + ConstantEffectSettings + + + + 0 + 0 + 400 + 300 + + + + + 0 + 0 + + + + + 400 + 300 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QLayout::SetMinimumSize + + + + + + 0 + 0 + + + + Level: + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + + + + + EnvelopeSettings + QWidget +
envelopesettings.h
+ 1 +
+
+ + +
diff --git a/deviceprober.cpp b/deviceprober.cpp new file mode 100644 index 0000000..3ead1ee --- /dev/null +++ b/deviceprober.cpp @@ -0,0 +1,66 @@ +#include "deviceprober.h" +#include "ffbdevice.h" +#include +#include +#include +#include +#include +#include + +const QDir DeviceProber::s_deviceNodesByID("/dev/input/by-id"); +const QString DeviceProber::res_ffbdeviceErrCap("FFB Device error"); + +DeviceProber::DeviceProber(QObject* parent) : + QObject(parent) +{ +} + +QStringList DeviceProber::listDevicesByID() +{ + QStringList devices = DeviceProber::s_deviceNodesByID.entryList(QDir::NoDotAndDotDot); + + foreach (const QString s, devices) + qDebug() << s; + + return devices; +} + +std::shared_ptr DeviceProber::openDeviceByID(const QString& id) +{ + QString path = DeviceProber::s_deviceNodesByID.absoluteFilePath(id); + /* Check if the device is already opened */ + for (std::shared_ptr dev : m_openedDevices) { + if (QString::compare(id, dev->id()) == 0) { + qDebug() << "Device" << id << "already opened"; + return dev; + } + } + + int fd = open(path.toLocal8Bit(), O_RDWR); + if (!fd) { + QMessageBox::critical(nullptr, res_ffbdeviceErrCap, "Cannot open device."); + return nullptr; + } + + int maxEffectCount; + int ret = ioctl(fd, EVIOCGEFFECTS, &maxEffectCount); + if (ret < 0) { + QMessageBox::critical(nullptr, res_ffbdeviceErrCap, "Cannot query maximum effects count.\nDevice probably does not support Force Feedback (errno " + QString::number(ret) + ")"); + close(fd); + return nullptr; + } + if (maxEffectCount < 1) { + QMessageBox::critical(nullptr, res_ffbdeviceErrCap, "Maximum effect count for this device is zero."); + close(fd); + return nullptr; + } + + std::shared_ptr device(new FFBDevice(fd, id, maxEffectCount)); + if (!device->queryDeviceCapabilities()) { + QMessageBox::critical(nullptr, res_ffbdeviceErrCap, "Unable to query device capabilities."); + return nullptr; + } + + m_openedDevices.push_back(device); + return device; +} diff --git a/deviceprober.h b/deviceprober.h new file mode 100644 index 0000000..e39ab75 --- /dev/null +++ b/deviceprober.h @@ -0,0 +1,29 @@ +#ifndef DEVICEPROBER_H +#define DEVICEPROBER_H + +#include "ffbdevice.h" +#include +#include +#include + +class DeviceProber : public QObject +{ + Q_OBJECT +public: + explicit DeviceProber(QObject* parent = 0); + QStringList listDevicesByID(); + std::shared_ptr openDeviceByID(const QString& id); + +private: + std::list> m_openedDevices; + + static const QDir s_deviceNodesByID; + static const QString res_ffbdeviceErrCap; + +signals: + +public slots: + +}; + +#endif // DEVICEPROBER_H diff --git a/effectsettings.cpp b/effectsettings.cpp new file mode 100644 index 0000000..cbe03b1 --- /dev/null +++ b/effectsettings.cpp @@ -0,0 +1,6 @@ +#include "effectsettings.h" + +EffectSettings::EffectSettings(QWidget* parent) : + QWidget(parent) +{ +} diff --git a/effectsettings.h b/effectsettings.h new file mode 100644 index 0000000..14f42ff --- /dev/null +++ b/effectsettings.h @@ -0,0 +1,20 @@ +#ifndef EFFECTSETTINGS_H +#define EFFECTSETTINGS_H + +#include "ffbeffectparameters.h" +#include + +class EffectSettings : public QWidget +{ + Q_OBJECT +public: + explicit EffectSettings(QWidget* parent = 0); + virtual bool fillFromParameters(const std::shared_ptr params) = 0; + +signals: + +public slots: + +}; + +#endif // EFFECTSETTINGS_H diff --git a/envelopesettings.cpp b/envelopesettings.cpp new file mode 100644 index 0000000..c70fae8 --- /dev/null +++ b/envelopesettings.cpp @@ -0,0 +1,52 @@ +#include "envelopesettings.h" +#include "ui_envelopesettings.h" +#include + +EnvelopeSettings::EnvelopeSettings(QWidget *parent) : + QWidget(parent), + ui(new Ui::EnvelopeSettings) +{ + ui->setupUi(this); +} + +QString EnvelopeSettings::attackLength() const +{ + return ui->qle_attackLength->text(); +} + +QString EnvelopeSettings::attackLevel() const +{ + return ui->qle_attackLevel->text(); +} + +QString EnvelopeSettings::fadeLength() const +{ + return ui->qle_fadeLength->text(); +} + +QString EnvelopeSettings::fadeLevel() const +{ + return ui->qle_fadeLevel->text(); +} + +bool EnvelopeSettings::fillFromParameters(const std::shared_ptr params) +{ + try { + const std::shared_ptr envParams = std::dynamic_pointer_cast(params); + + ui->qle_attackLength->setText(QString::number(envParams->attackLength)); + ui->qle_attackLevel->setText(QString::number(envParams->attackLevel)); + ui->qle_fadeLength->setText(QString::number(envParams->fadeLength)); + ui->qle_fadeLevel->setText(QString::number(envParams->fadeLevel)); + return true; + } catch (std::bad_cast& ex) { + qCritical(ex.what()); + return false; + } + return false; +} + +EnvelopeSettings::~EnvelopeSettings() +{ + delete ui; +} diff --git a/envelopesettings.h b/envelopesettings.h new file mode 100644 index 0000000..d8723fb --- /dev/null +++ b/envelopesettings.h @@ -0,0 +1,30 @@ +#ifndef ENVELOPESETTINGS_H +#define ENVELOPESETTINGS_H + +#include "ffbeffectparameters.h" +#include "ffbenvelopeparameters.h" +#include + +namespace Ui { + class EnvelopeSettings; +} + +class EnvelopeSettings : public QWidget +{ + Q_OBJECT + +public: + explicit EnvelopeSettings(QWidget *parent = 0); + ~EnvelopeSettings(); + bool fillFromParameters(const std::shared_ptr params); + + QString attackLength() const; + QString attackLevel() const; + QString fadeLength() const; + QString fadeLevel() const; + +private: + Ui::EnvelopeSettings* ui; +}; + +#endif // ENVELOPESETTINGS_H diff --git a/envelopesettings.ui b/envelopesettings.ui new file mode 100644 index 0000000..499d92a --- /dev/null +++ b/envelopesettings.ui @@ -0,0 +1,101 @@ + + + EnvelopeSettings + + + + 0 + 0 + 400 + 129 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Attack level: + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Attack length: + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Fade level: + + + + + + + Fade length: + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + diff --git a/ffbconstanteffect.cpp b/ffbconstanteffect.cpp new file mode 100644 index 0000000..e713009 --- /dev/null +++ b/ffbconstanteffect.cpp @@ -0,0 +1,56 @@ +#include "ffbconstanteffect.h" + +FFBConstantEffect::FFBConstantEffect() : + FFBEffect(FFBEffectTypes::CONSTANT) +{ +} + +struct ff_effect* FFBConstantEffect::createFFStruct() +{ + /* Set up generic effect parameters */ + struct ff_effect* eff = FFBEffect::createFFStruct(m_params); + + eff->type = FF_CONSTANT; + + eff->u.constant.envelope.attack_length = m_params->attackLength; + eff->u.constant.envelope.attack_level = m_params->attackLevel; + eff->u.constant.envelope.fade_length = m_params->fadeLength; + eff->u.constant.envelope.fade_level= m_params->fadeLevel; + + eff->u.constant.level = m_params->level; + + return eff; +} + +bool FFBConstantEffect::setParameters(const std::shared_ptr params) +{ + try { + return setParameters(std::dynamic_pointer_cast(params)); + } catch (const std::bad_cast&) { + return false; + } + return false; +} + +bool FFBConstantEffect::setParameters(const std::shared_ptr params) +{ + if (!checkGenericParameters(params)) + return false; + + if (!checkBoundsInclusive(params->attackLength, 0, 0xFFFF)) + return false; + if (!checkBoundsInclusive(params->attackLevel, 0, 0xFFFF)) + return false; + if (!checkBoundsInclusive(params->fadeLevel, 0, 0xFFFF)) + return false; + if (!checkBoundsInclusive(params->fadeLevel, 0, 0xFFFF)) + return false; + + if (!checkBoundsInclusive(params->level, -0x7FFF, 0x7FFF)) { + reportError("Level out of bounds."); + return false; + } + + m_params = params; + return true; +} diff --git a/ffbconstanteffect.h b/ffbconstanteffect.h new file mode 100644 index 0000000..05a907e --- /dev/null +++ b/ffbconstanteffect.h @@ -0,0 +1,20 @@ +#ifndef FFBCONSTANTEFFECT_H +#define FFBCONSTANTEFFECT_H + +#include "ffbeffect.h" +#include "ffbconstanteffectparameters.h" + +class FFBConstantEffect : public FFBEffect +{ +public: + explicit FFBConstantEffect(); + struct ff_effect* createFFStruct(); + inline const std::shared_ptr parameters() const { return m_params; } + bool setParameters(const std::shared_ptr params); + bool setParameters(const std::shared_ptr params); + +private: + std::shared_ptr m_params; +}; + +#endif // FFBCONSTANTEFFECT_H diff --git a/ffbconstanteffectparameters.cpp b/ffbconstanteffectparameters.cpp new file mode 100644 index 0000000..43f7b13 --- /dev/null +++ b/ffbconstanteffectparameters.cpp @@ -0,0 +1,18 @@ +#include "ffbconstanteffectparameters.h" + +FFBConstantEffectParameters::FFBConstantEffectParameters() : + FFBEffectParameters(), FFBEnvelopeParameters(), + level(0) +{} + +bool FFBConstantEffectParameters::levelFromString(const QString& level) +{ + bool ok; + this->level = level.toInt(&ok); + + return ok; +} + +FFBConstantEffectParameters::~FFBConstantEffectParameters() +{ +} diff --git a/ffbconstanteffectparameters.h b/ffbconstanteffectparameters.h new file mode 100644 index 0000000..2f61009 --- /dev/null +++ b/ffbconstanteffectparameters.h @@ -0,0 +1,18 @@ +#ifndef FFBCONSTANTEFFECTPARAMETERS_H +#define FFBCONSTANTEFFECTPARAMETERS_H + +#include "ffbeffectparameters.h" +#include "ffbenvelopeparameters.h" + +class FFBConstantEffectParameters : public FFBEffectParameters, public FFBEnvelopeParameters +{ +public: + FFBConstantEffectParameters(); + ~FFBConstantEffectParameters(); + + bool levelFromString(const QString& level); + + int level; +}; + +#endif // FFBCONSTANTEFFECTPARAMETERS_H diff --git a/ffbdevice.cpp b/ffbdevice.cpp new file mode 100644 index 0000000..8314a3a --- /dev/null +++ b/ffbdevice.cpp @@ -0,0 +1,301 @@ +#include "ffbdevice.h" +#include "ffbeffectfactory.h" +#include + +const quint8 FFBDevice::BITS_PER_LONG = sizeof(unsigned long) * 8; + +FFBDevice::FFBDevice(const int fd, const QString& id, const int maxEffectCount, QObject* parent) : + QObject(parent), + c_fd(fd), + c_id(id), + c_maxEffectCount(maxEffectCount) +{ + for (int i = 0; i < maxEffectCount; i++) + m_effects.push_back(FFBEffectFactory::createEffect(FFBEffectTypes::NONE)); +} + +QStringList FFBDevice::availableEffectsList() const +{ + QStringList list; + + for (const FFBEffectTypes e : m_availableEffects) + list << effectName(e); + + return list; +} + +QStringList FFBDevice::availableWaveformsList() const +{ + QStringList list; + + for (const PeriodicWaveforms w : m_availablePeriodicWaveforms) + list << waveformName(w); + + return list; +} +QString FFBDevice::effectName(const FFBEffectTypes effect) const +{ + switch (effect) { + case FFBEffectTypes::CONSTANT: + return "Constant force"; + case FFBEffectTypes::PERIODIC: + return "Periodic force"; + case FFBEffectTypes::RAMP: + return "Ramp"; + case FFBEffectTypes::SPRING: + return "Spring"; + case FFBEffectTypes::FRICTION: + return "Friction"; + case FFBEffectTypes::DAMPER: + return "Damper"; + case FFBEffectTypes::RUMBLE: + return "Rumble"; + case FFBEffectTypes::INERTIA: + return "Inertia"; + default: + return "Unknown effect"; + } +} + +const std::shared_ptr FFBDevice::effectParameters(const int idx) +{ + if (idx >= c_maxEffectCount) + return nullptr; + + return m_effects[idx]->parameters(); +} + +FFBEffect::FFBEffectStatus FFBDevice::effectStatusByIdx(const int idx) const +{ + if (m_effects[idx] == nullptr) + return FFBEffect::FFBEffectStatus::NOT_LOADED; + + return m_effects[idx]->status(); +} + +FFBEffectTypes FFBDevice::effectTypeByEffectIdx(const int idx) const +{ + return m_effects[idx]->type(); +} + +unsigned int FFBDevice::effectTypeToIdx(FFBEffectTypes type) +{ + for (unsigned int i = 0; i < m_availableEffects.size(); i++) { + if (m_availableEffects[i] == type) + return i; + } + qWarning() << "Effect type no found in the list!"; + return 0; +} + +bool FFBDevice::isEffectUpdateable(const std::shared_ptr effect, const std::shared_ptr params, const FFBEffectTypes type) +{ + if (effect->type() != type) + return false; + if (type == FFBEffectTypes::PERIODIC) { + const std::shared_ptr pParams; + try { + PeriodicWaveforms waveform = std::dynamic_pointer_cast(effect->parameters())->waveform; + if (waveform != std::dynamic_pointer_cast(params)->waveform) + return false; + } catch (std::bad_cast& ex) { + qDebug() << ex.what(); + return false; + } + } + return true; +} + +QString FFBDevice::waveformName(const PeriodicWaveforms waveform) const +{ + switch (waveform) { + case PeriodicWaveforms::SQUARE: + return "Square"; + case PeriodicWaveforms::TRIANGLE: + return "Triangle"; + case PeriodicWaveforms::SINE: + return "Sine"; + case PeriodicWaveforms::SAW_UP: + return "Saw up"; + case PeriodicWaveforms::SAW_DOWN: + return "Saw down"; + default: + return "Unknown waveform"; + } +} + +bool FFBDevice::hasEffect(FFBEffectTypes id) const +{ + for (const FFBEffectTypes e : m_availableEffects) + if (e == id) return true; + + return false; +} + +bool FFBDevice::hasPeriodicWaveform(PeriodicWaveforms id) const +{ + for (const PeriodicWaveforms w : m_availablePeriodicWaveforms) + if (w == id) return true; + + return false; +} + +bool FFBDevice::queryDeviceCapabilities() +{ + unsigned long caps[4]; + int ret = ioctl(c_fd, EVIOCGBIT(EV_FF, sizeof(caps)), caps); + if (ret < 0) + return false; + + /* Query FFB effects this device can do */ + if (testBit(FF_CONSTANT, caps)) + m_availableEffects.push_back(FFBEffectTypes::CONSTANT); + if (testBit(FF_PERIODIC, caps)) + m_availableEffects.push_back(FFBEffectTypes::PERIODIC); + if (testBit(FF_RAMP, caps)) + m_availableEffects.push_back(FFBEffectTypes::RAMP); + if (testBit(FF_SPRING, caps)) + m_availableEffects.push_back(FFBEffectTypes::SPRING); + if (testBit(FF_FRICTION, caps)) + m_availableEffects.push_back(FFBEffectTypes::FRICTION); + if (testBit(FF_DAMPER, caps)) + m_availableEffects.push_back(FFBEffectTypes::DAMPER); + if (testBit(FF_INERTIA, caps)) + m_availableEffects.push_back(FFBEffectTypes::INERTIA); + + /* Query waveforms for PERIODIC if the device supports it */ + if (hasEffect(FFBEffectTypes::PERIODIC)) { + if (testBit(FF_SQUARE, caps)) + m_availablePeriodicWaveforms.push_back(PeriodicWaveforms::SQUARE); + if (testBit(FF_TRIANGLE, caps)) + m_availablePeriodicWaveforms.push_back(PeriodicWaveforms::TRIANGLE); + if (testBit(FF_SINE, caps)) + m_availablePeriodicWaveforms.push_back(PeriodicWaveforms::SINE); + if (testBit(FF_SAW_UP, caps)) + m_availablePeriodicWaveforms.push_back(PeriodicWaveforms::SAW_UP); + if (testBit(FF_SAW_DOWN, caps)) + m_availablePeriodicWaveforms.push_back(PeriodicWaveforms::SAW_DOWN); + } + + return true; +} + +bool FFBDevice::removeEffect(const int idx) +{ + if (m_effects[idx]->status() == FFBEffect::FFBEffectStatus::NOT_LOADED) + return true; + + if (!stopEffect(idx)) + return false; + + int internalIdx = m_effects[idx]->internalIdx(); + int ret = ioctl(c_fd, EVIOCRMFF, internalIdx); + if (ret < 0) + return false; + + m_effects[idx]->setStatus(FFBEffect::FFBEffectStatus::NOT_LOADED); + return true; +} + +bool FFBDevice::startEffect(const int idx, FFBEffectTypes type, std::shared_ptr params) +{ + if (idx < 0 || idx > c_maxEffectCount) { + qDebug() << "Effect index out of bounds"; + return false; + } + + /* There is no effect in the selected slot */ + if (m_effects[idx]->type() == FFBEffectTypes::NONE) { + std::shared_ptr effect = FFBEffectFactory::createEffect(type); + if (effect == nullptr) { + qWarning() << "Unable to create FFBEffect"; + return false; + } + m_effects[idx] = effect; + qDebug() << "Creating new effect"; + } else { + if (!isEffectUpdateable(m_effects[idx], params, type)) { + removeEffect(idx); + m_effects[idx] = FFBEffectFactory::createEffect(type); + if (m_effects[idx] == nullptr) { + qDebug() << "Effect was not recreated."; + return false; + } + qDebug() << "Recreating effect" << idx; + } else + qDebug() << "Updating effect" << idx; + } + + if (!m_effects[idx]->setParameters(params)) { + qDebug() << "Unable to set effect parameters, some values are probably invalid."; + return false; + } + + struct ff_effect* kernelEff = nullptr; + kernelEff = m_effects[idx]->createFFStruct(); + if (kernelEff == nullptr) { + qDebug() << "struct ff_effect not created"; + return false; + } + int ret = uploadEffect(kernelEff); + if (ret < 0) { + qDebug() << "Effect not uploaded"; + delete kernelEff; + return false; + } + + m_effects[idx]->setInternalIdx(kernelEff->id); + m_effects[idx]->setStatus(FFBEffect::FFBEffectStatus::STOPPED); + + /* Start playback */ + struct input_event evt; + evt.type = EV_FF; + evt.code = kernelEff->id; + evt.value = 1; + + ret = write(c_fd, &evt, sizeof(struct input_event)); + if (ret != sizeof(struct input_event)) { + qDebug() << "Effect not started"; + delete kernelEff; + return false; + } + + m_effects[idx]->setStatus(FFBEffect::FFBEffectStatus::PLAYING); + + delete kernelEff; + return true; +} + +bool FFBDevice::stopEffect(const int idx) +{ + if (m_effects[idx] == nullptr) + return true; + + if (m_effects[idx]->status() != FFBEffect::FFBEffectStatus::PLAYING) + return true; + + int internalIdx = m_effects[idx]->internalIdx(); + + struct input_event evt; + evt.type = EV_FF; + evt.code = internalIdx; + evt.value = 0; + + int ret = write(c_fd, &evt, sizeof(struct input_event)); + if (ret != sizeof(struct input_event)) + return false; + + m_effects[idx]->setStatus(FFBEffect::FFBEffectStatus::STOPPED); + return true; +} + +int FFBDevice::uploadEffect(struct ff_effect* effect) +{ + int ret = ioctl(c_fd, EVIOCSFF, effect); + if (ret < 0) { + qDebug() << "Error while uploading effect"; + return ret; + } + + return effect->id; +} diff --git a/ffbdevice.h b/ffbdevice.h new file mode 100644 index 0000000..80f2744 --- /dev/null +++ b/ffbdevice.h @@ -0,0 +1,61 @@ +#ifndef FFBDEVICE_H +#define FFBDEVICE_H + +#include "ffbeffect.h" +#include +#include +#include +#include +#include +#include +#include +#include + +class FFBDevice : public QObject +{ + Q_OBJECT +public: + + explicit FFBDevice(const int fd, const QString& id, const int maxEffectCount, QObject* parent = 0); + QStringList availableEffectsList() const; + QStringList availableWaveformsList() const; + QString effectName(const FFBEffectTypes effect) const; + const std::shared_ptr effectParameters(const int idx); + FFBEffect::FFBEffectStatus effectStatusByIdx(const int idx) const; + inline FFBEffectTypes effectTypeFromSelectionIdx(const int idx) const { return m_availableEffects[idx]; } + unsigned int effectTypeToIdx(FFBEffectTypes type); + FFBEffectTypes effectTypeByEffectIdx(const int idx) const; + bool hasEffect(FFBEffectTypes id) const; + bool hasPeriodicWaveform(PeriodicWaveforms id) const; + inline const QString& id() const { return c_id; } + inline int maxEffectCount() const { return c_maxEffectCount; } + bool queryDeviceCapabilities(); + bool removeEffect(const int idx); + bool startEffect(const int idx, FFBEffectTypes type, std::shared_ptr params); + bool stopEffect(const int idx); + QString waveformName(const PeriodicWaveforms waveform) const; + inline PeriodicWaveforms waveformByIdx(const int idx) const { return m_availablePeriodicWaveforms[idx]; } + +private: + bool isEffectUpdateable(const std::shared_ptr effect, const std::shared_ptr params, const FFBEffectTypes type); + int uploadEffect(struct ff_effect* effect); + std::vector m_availableEffects; + std::vector m_availablePeriodicWaveforms; + std::vector> m_effects; + + const int c_fd; + const QString c_id; + const int c_maxEffectCount; + + static inline unsigned long longIdx(unsigned long bit) { return bit / BITS_PER_LONG; } + static inline unsigned long offset(unsigned long bit) { return bit % BITS_PER_LONG; } + static inline bool testBit(unsigned long bit, unsigned long* array) { return (array[longIdx(bit)] >> offset(bit)) & 1; } + + static const quint8 BITS_PER_LONG; +signals: + +public slots: + +}; + +#endif // FFBDEVICE_H diff --git a/ffbeffect.cpp b/ffbeffect.cpp new file mode 100644 index 0000000..e0493fc --- /dev/null +++ b/ffbeffect.cpp @@ -0,0 +1,57 @@ +#include "ffbeffect.h" +#include + +FFBEffect::FFBEffect(FFBEffectTypes type) +{ + m_internalIdx = -1; + m_status = FFBEffectStatus::NOT_LOADED; + m_type = type; +} + +struct ff_effect* FFBEffect::createFFStruct(const std::shared_ptr params) +{ + struct ff_effect* eff = new struct ff_effect; + memset(eff, 0, sizeof(struct ff_effect)); + + eff->id = m_internalIdx; + eff->direction = params->direction; + eff->replay.delay = params->replayDelay; + eff->replay.length = params->replayLength; + + return eff; +} + +void FFBEffect::reportError(const QString& errorMsg) const +{ + QMessageBox::warning(nullptr, "FFB effect error", errorMsg); +} + +bool FFBEffect::checkGenericParameters(const std::shared_ptr params) +{ + if (!checkBoundsInclusive(params->direction, 0, 0xFFFF)) { + reportError("Direction out of bounds."); + return false; + } + + if (!checkBoundsInclusive(params->replayDelay, 0, 0xFFFF)) { + reportError("Replay delay out of bounds."); + return false; + } + + if (!checkBoundsInclusive(params->replayLength, 0, 0xFFFF)) { + reportError("Replay length out of bounds."); + return false; + } + + return true; +} + +bool FFBEffect::operator==(const FFBEffect& other) const +{ + return this->type() == other.type(); +} + +bool FFBEffect::operator!=(const FFBEffect& other) const +{ + return !(*this == other); +} diff --git a/ffbeffect.h b/ffbeffect.h new file mode 100644 index 0000000..2d678e8 --- /dev/null +++ b/ffbeffect.h @@ -0,0 +1,42 @@ +#ifndef FFBEFFECT_H +#define FFBEFFECT_H + +#include "ffbeffectparameters.h" +#include "globals.h" +#include +#include + +class FFBEffect { +public: + enum class FFBEffectStatus { PLAYING, STOPPED, NOT_LOADED }; + + explicit FFBEffect(FFBEffectTypes type); + virtual struct ff_effect* createFFStruct() = 0; + inline int internalIdx() const { return m_internalIdx; } + virtual const std::shared_ptr parameters() const = 0; + void reportError(const QString& errorMsg) const; + inline void setInternalIdx(int idx) { m_internalIdx = idx; } + virtual bool setParameters(const std::shared_ptr params) = 0; + inline void setStatus(FFBEffectStatus status) { m_status = status; } + inline FFBEffectStatus status() const { return m_status; } + inline FFBEffectTypes type() const { return m_type; } + + virtual bool operator==(const FFBEffect&) const; + virtual bool operator!=(const FFBEffect&) const; + +protected: + struct ff_effect* createFFStruct(const std::shared_ptr params); + bool checkGenericParameters(const std::shared_ptr params); + +private: + int m_internalIdx; + FFBEffectStatus m_status; + FFBEffectTypes m_type; + +signals: + +public slots: + +}; + +#endif // FFBEFFECT_H diff --git a/ffbeffectfactory.cpp b/ffbeffectfactory.cpp new file mode 100644 index 0000000..4501c2b --- /dev/null +++ b/ffbeffectfactory.cpp @@ -0,0 +1,19 @@ +#include "ffbeffectfactory.h" + +FFBEffectFactory::FFBEffectFactory() +{ +} + +std::shared_ptr FFBEffectFactory::createEffect(FFBEffectTypes type) +{ + switch (type) { + case FFBEffectTypes::NONE: + return std::shared_ptr(new FFBNullEffect()); + case FFBEffectTypes::CONSTANT: + return std::shared_ptr(new FFBConstantEffect()); + case FFBEffectTypes::PERIODIC: + return std::shared_ptr(new FFBPeriodicEffect()); + default: + return nullptr; + } +} diff --git a/ffbeffectfactory.h b/ffbeffectfactory.h new file mode 100644 index 0000000..78d0b41 --- /dev/null +++ b/ffbeffectfactory.h @@ -0,0 +1,19 @@ +#ifndef FFBEFFECTFACTORY_H +#define FFBEFFECTFACTORY_H + +#include "globals.h" +#include "ffbconstanteffect.h" +#include "ffbnulleffect.h" +#include "ffbperiodiceffect.h" + +class FFBEffectFactory +{ +public: + static std::shared_ptr createEffect(FFBEffectTypes type); + +private: + FFBEffectFactory(); + +}; + +#endif // FFBEFFECTFACTORY_H diff --git a/ffbeffectparameters.cpp b/ffbeffectparameters.cpp new file mode 100644 index 0000000..9f3d705 --- /dev/null +++ b/ffbeffectparameters.cpp @@ -0,0 +1,36 @@ +#include "ffbeffectparameters.h" + +FFBEffectParameters::FFBEffectParameters() : + direction(0), + replayDelay(0), + replayLength(0) +{ +} + +bool FFBEffectParameters::directionFromString(const QString& direction) +{ + bool ok; + this->direction = direction.toInt(&ok); + + return ok; +} + +bool FFBEffectParameters::replayDelayFromString(const QString& replayDelay) +{ + bool ok; + this->replayDelay = replayDelay.toInt(&ok); + + return ok; +} + +bool FFBEffectParameters::replayLengthFromString(const QString& replayLength) +{ + bool ok; + this->replayLength = replayLength.toInt(&ok); + + return ok; +} + +FFBEffectParameters::~FFBEffectParameters() +{ +} diff --git a/ffbeffectparameters.h b/ffbeffectparameters.h new file mode 100644 index 0000000..f88b5f0 --- /dev/null +++ b/ffbeffectparameters.h @@ -0,0 +1,23 @@ +#ifndef FFBEFFECTPARAMETERS_H +#define FFBEFFECTPARAMETERS_H + +#include "globals.h" +#include +#include + +class FFBEffectParameters +{ +public: + FFBEffectParameters(); + virtual ~FFBEffectParameters(); + + bool directionFromString(const QString& direction); + bool replayDelayFromString(const QString& replayDelay); + bool replayLengthFromString(const QString& replayLength); + + int direction; + int replayDelay; + int replayLength; +}; + +#endif // FFBEFFECTPARAMETERS_H diff --git a/ffbenvelopeparameters.cpp b/ffbenvelopeparameters.cpp new file mode 100644 index 0000000..b1eddea --- /dev/null +++ b/ffbenvelopeparameters.cpp @@ -0,0 +1,33 @@ +#include "ffbenvelopeparameters.h" + +FFBEnvelopeParameters::FFBEnvelopeParameters() +{} + +bool FFBEnvelopeParameters::attackLengthFromString(const QString& attackLength) +{ + bool ok; + this->attackLength = attackLength.toInt(&ok); + return ok; +} + +bool FFBEnvelopeParameters::attackLevelFromString(const QString& attackLevel) +{ + bool ok; + this->attackLevel = attackLevel.toInt(&ok); + return ok; +} + +bool FFBEnvelopeParameters::fadeLengthFromString(const QString& fadeLength) +{ + bool ok; + this->fadeLength = fadeLength.toInt(&ok); + return ok; +} + + +bool FFBEnvelopeParameters::fadeLevelFromString(const QString& fadeLevel) +{ + bool ok; + this->fadeLevel = fadeLevel.toInt(&ok); + return ok; +} diff --git a/ffbenvelopeparameters.h b/ffbenvelopeparameters.h new file mode 100644 index 0000000..a84a4f2 --- /dev/null +++ b/ffbenvelopeparameters.h @@ -0,0 +1,21 @@ +#ifndef FFBENVELOPEPARAMETERS_H +#define FFBENVELOPEPARAMETERS_H + +#include + +class FFBEnvelopeParameters +{ +public: + FFBEnvelopeParameters(); + bool attackLengthFromString(const QString& attackLength); + bool attackLevelFromString(const QString& attackLevel); + bool fadeLengthFromString(const QString& fadeLength); + bool fadeLevelFromString(const QString& fadeLevel); + + int attackLength; + int attackLevel; + int fadeLength; + int fadeLevel; +}; + +#endif // FFBENVELOPEPARAMETERS_H diff --git a/ffbnulleffect.cpp b/ffbnulleffect.cpp new file mode 100644 index 0000000..c9eac5f --- /dev/null +++ b/ffbnulleffect.cpp @@ -0,0 +1,5 @@ +#include "ffbnulleffect.h" + +FFBNullEffect::FFBNullEffect() : + FFBEffect(FFBEffectTypes::NONE) +{} diff --git a/ffbnulleffect.h b/ffbnulleffect.h new file mode 100644 index 0000000..96a1ffe --- /dev/null +++ b/ffbnulleffect.h @@ -0,0 +1,15 @@ +#ifndef FFBNULLEFFECT_H +#define FFBNULLEFFECT_H + +#include "ffbeffect.h" + +class FFBNullEffect : public FFBEffect +{ +public: + explicit FFBNullEffect(); + inline struct ff_effect* createFFStruct() { return nullptr; } + inline const std::shared_ptr parameters() const { return nullptr; } + inline bool setParameters(const std::shared_ptr params) { return false; } +}; + +#endif // FFBNULLEFFECT_H diff --git a/ffbperiodiceffect.cpp b/ffbperiodiceffect.cpp new file mode 100644 index 0000000..2099deb --- /dev/null +++ b/ffbperiodiceffect.cpp @@ -0,0 +1,129 @@ +#include "ffbperiodiceffect.h" +#include + +FFBPeriodicEffect::FFBPeriodicEffect() : + FFBEffect(FFBEffectTypes::PERIODIC) +{} + +struct ff_effect* FFBPeriodicEffect::createFFStruct() +{ + struct ff_effect* eff = FFBEffect::createFFStruct(m_params); + if (eff == nullptr) + return nullptr; + + eff->type = FF_PERIODIC; + + eff->u.periodic.envelope.attack_length = m_params->attackLength; + eff->u.periodic.envelope.attack_level = m_params->attackLevel; + eff->u.periodic.envelope.fade_length = m_params->fadeLength; + eff->u.periodic.envelope.fade_level = m_params->fadeLevel; + + eff->u.periodic.magnitude = m_params->magnitude; + eff->u.periodic.offset = m_params->offset; + eff->u.periodic.period = m_params->period; + eff->u.periodic.phase = m_params->phase; + + switch (m_params->waveform) { + case PeriodicWaveforms::SINE: + eff->u.periodic.waveform= FF_SINE; + break; + case PeriodicWaveforms::SQUARE: + eff->u.periodic.waveform = FF_SQUARE; + break; + case PeriodicWaveforms::SAW_DOWN: + eff->u.periodic.waveform = FF_SAW_DOWN; + break; + case PeriodicWaveforms::SAW_UP: + eff->u.periodic.waveform = FF_SAW_UP; + break; + case PeriodicWaveforms::TRIANGLE: + eff->u.periodic.waveform = FF_TRIANGLE; + break; + case PeriodicWaveforms::NONE: + delete eff; + return nullptr; + } + + return eff; +} + +bool FFBPeriodicEffect::setParameters(const std::shared_ptr params) +{ + try { + return setParameters(std::dynamic_pointer_cast(params)); + } catch (const std::bad_cast& ex) { + reportError("Invalid effect parameters object " + QString(ex.what())); + return false; + } +} + +bool FFBPeriodicEffect::setParameters(const std::shared_ptr params) +{ + if (!checkGenericParameters(params)) + return false; + + if (!checkBoundsInclusive(params->attackLength, 0, 0xFFFF)) { + reportError("Attack length out of bounds."); + return false; + } + + if (!checkBoundsInclusive(params->attackLevel, 0, 0xFFFF)) { + reportError("Attack level out of bounds."); + return false; + } + + if (!checkBoundsInclusive(params->fadeLength, 0, 0xFFFF)) { + reportError("Fade length out of bounds."); + return false; + } + + if (!checkBoundsInclusive(params->fadeLevel, 0, 0xFFFF)) { + reportError("Fade level out of bounds."); + return false; + } + + if (!checkBoundsInclusive(params->magnitude, -0x7FFF, 0x7FFF)) { + reportError("Magnitude out of bounds."); + return false; + } + + if (!checkBoundsInclusive(params->offset, -0x7FFF, 0x7FFF)) { + reportError("Offset out of bounds."); + return false; + } + + if (!checkBoundsInclusive(params->period, 0, 0xFFFF)) { + reportError("Period out of bounds."); + return false; + } + + if (!checkBoundsInclusive(params->phase, 0, 0xFFFF)) { + reportError("Phase out of bounds."); + return false; + } + + if (params->waveform == PeriodicWaveforms::NONE) { + reportError("Invalid waveform type."); + return false; + } + + m_params = params; + return true; +} + +bool FFBPeriodicEffect::operator==(const FFBEffect& other) const +{ + if (this->type() != other.type()) + return false; + else + return this->m_params->waveform == dynamic_cast(other).m_params->waveform; +} + +bool FFBPeriodicEffect::operator!=(const FFBEffect& other) const +{ + return !(*this == other); +} + +FFBPeriodicEffect::~FFBPeriodicEffect() +{ +} diff --git a/ffbperiodiceffect.h b/ffbperiodiceffect.h new file mode 100644 index 0000000..2a5b5ad --- /dev/null +++ b/ffbperiodiceffect.h @@ -0,0 +1,23 @@ +#ifndef FFBPERIODICEFFECT_H +#define FFBPERIODICEFFECT_H + +#include "ffbeffect.h" +#include "ffbperiodiceffectparameters.h" + +class FFBPeriodicEffect : public FFBEffect +{ +public: + FFBPeriodicEffect(); + ~FFBPeriodicEffect(); + struct ff_effect* createFFStruct(); + inline const std::shared_ptr parameters() const { return m_params; } + bool setParameters(const std::shared_ptr params); + bool setParameters(const std::shared_ptr params); + bool operator==(const FFBEffect& other) const; + bool operator!=(const FFBEffect& other) const; + +private: + std::shared_ptr m_params; +}; + +#endif // FFBPERIODICEFFECT_H diff --git a/ffbperiodiceffectparameters.cpp b/ffbperiodiceffectparameters.cpp new file mode 100644 index 0000000..538e1b0 --- /dev/null +++ b/ffbperiodiceffectparameters.cpp @@ -0,0 +1,45 @@ +#include "ffbperiodiceffectparameters.h" + +FFBPeriodicEffectParameters::FFBPeriodicEffectParameters() : + FFBEffectParameters(), FFBEnvelopeParameters(), + magnitude(0), + offset(0), + period(0), + phase(0), + waveform(PeriodicWaveforms::NONE) +{ +} + +bool FFBPeriodicEffectParameters::magnitudeFromString(const QString& magnitude) +{ + bool ok; + this->magnitude = magnitude.toInt(&ok); + return ok; +} + +bool FFBPeriodicEffectParameters::offsetFromString(const QString& offset) +{ + bool ok; + this->offset = offset.toInt(&ok); + return ok; +} + +bool FFBPeriodicEffectParameters::periodFromString(const QString& period) +{ + bool ok; + this->period = period.toInt(&ok); + return ok; +} + +bool FFBPeriodicEffectParameters::phaseFromString(const QString& phase) +{ + bool ok; + this->phase = phase.toInt(&ok); + return ok; +} + +void FFBPeriodicEffectParameters::waveformFromIdx(PeriodicWaveforms waveform) +{ + this->waveform = waveform; +} + diff --git a/ffbperiodiceffectparameters.h b/ffbperiodiceffectparameters.h new file mode 100644 index 0000000..f5d1206 --- /dev/null +++ b/ffbperiodiceffectparameters.h @@ -0,0 +1,25 @@ +#ifndef FFBPERIODICEFFECTPARAMETERS_H +#define FFBPERIODICEFFECTPARAMETERS_H + +#include "ffbeffectparameters.h" +#include "ffbenvelopeparameters.h" + +class FFBPeriodicEffectParameters : public FFBEffectParameters, public FFBEnvelopeParameters +{ +public: + FFBPeriodicEffectParameters(); + + bool magnitudeFromString(const QString& magnitude); + bool offsetFromString(const QString& offset); + bool periodFromString(const QString& period); + bool phaseFromString(const QString& phase); + void waveformFromIdx(PeriodicWaveforms waveform); + + int magnitude; + int offset; + int period; + int phase; + PeriodicWaveforms waveform; +}; + +#endif // FFBPERIODICEFFECTPARAMETERS_H diff --git a/globals.h b/globals.h new file mode 100644 index 0000000..2ee051a --- /dev/null +++ b/globals.h @@ -0,0 +1,23 @@ +#ifndef GLOBALS_H +#define GLOBALS_H + +enum class FFBEffectTypes { NONE, CONSTANT, PERIODIC, RAMP, SPRING, FRICTION, DAMPER, RUMBLE, INERTIA }; +enum class PeriodicWaveforms { NONE, SQUARE, TRIANGLE, SINE, SAW_UP, SAW_DOWN }; + +template inline bool checkBoundsInclusive(const T& val, const T& min, const T& max) +{ + if (val >= min && val <= max) + return true; + else + return false; +} + +template inline bool checkBoundsExclusive(const T& val, const T& min, const T& max) +{ + if (val > min && val < max) + return true; + else + return false; +} + +#endif // GLOBALS_H diff --git a/helpers.h b/helpers.h new file mode 100644 index 0000000..a81a737 --- /dev/null +++ b/helpers.h @@ -0,0 +1,9 @@ +#ifndef HELPERS_H +#define HELPERS_H + +static inline int bitsToLongs(unsigned long bits) +{ + return (bits + 8 * sizeof(unsigned long) - 1) / (8 *sizeof(unsigned long)); +} + +#endif // HELPERS_H diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..afc9b84 --- /dev/null +++ b/main.cpp @@ -0,0 +1,13 @@ +#include "deviceprober.h" +#include "mainwindow.h" +#include + +int main(int argc, char** argv) +{ + QApplication myApp(argc, argv); + std::shared_ptr prober(new DeviceProber); + MainWindow* mWin = new MainWindow(prober); + + mWin->show(); + return myApp.exec(); +} diff --git a/mainwindow.cpp b/mainwindow.cpp new file mode 100644 index 0000000..9cefcfb --- /dev/null +++ b/mainwindow.cpp @@ -0,0 +1,270 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include +#include + +const QString MainWindow::res_effectNotLoaded("Not loaded"); +const QString MainWindow::res_effectPlaying("Playing"); +const QString MainWindow::res_effectStopped("Stopped"); +const QString MainWindow::res_inputFormatErrCap("Invalid input format."); + +MainWindow::MainWindow(std::shared_ptr prober, QWidget* parent) : + QMainWindow(parent), + m_prober(prober), + ui(new Ui::MainWindow) +{ + ui->setupUi(this); + + /* HACK: Remove all default widgets from the stack */ + for (int i = 0; i < ui->qstw_effectSpecifics->count(); i++) + ui->qstw_effectSpecifics->removeWidget(ui->qstw_effectSpecifics->widget(i)); + + m_constantEffSet = new ConstantEffectSettings(); + m_periodicEffSet = new PeriodicEffectSettings(); + ui->qstw_effectSpecifics->addWidget(m_constantEffSet); + ui->qstw_effectSpecifics->addWidget(m_periodicEffSet); + + fillDeviceList(); + connect(ui->cbox_devices, SIGNAL(activated(const QString&)), this, SLOT(onDeviceSelected(const QString&))); + connect(ui->cbox_effectSlots, SIGNAL(activated(const int)), this, SLOT(onEffectSlotSelected(const int))); + connect(ui->cbox_effectTypes, SIGNAL(activated(const int)), this, SLOT(onEffectTypeSelected(const int))); + connect(ui->qpb_refreshDevices, SIGNAL(clicked()), this, SLOT(onRefreshDevicesClicked())); + connect(ui->qpb_start, SIGNAL(clicked()), this, SLOT(onStartEffectClicked())); + connect(ui->qpb_stop, SIGNAL(clicked()), this, SLOT(onStopEffectClicked())); +} + +EffectSettings* MainWindow::effectSettingsByType(FFBEffectTypes type) +{ + switch (type) { + case FFBEffectTypes::CONSTANT: + return m_constantEffSet; + case FFBEffectTypes::PERIODIC: + return m_periodicEffSet; + default: + abort(); + } +} + +void MainWindow::fillDeviceList() +{ + ui->cbox_devices->clear(); + ui->cbox_devices->addItems(m_prober->listDevicesByID()); +} +void MainWindow::fillEffectSlotsList(const int idx) +{ + ui->cbox_effectSlots->clear(); + for (int i = 1; i <= idx; i++) + ui->cbox_effectSlots->addItem(QString::number(i)); +} + +void MainWindow::fillEffectTypesList(const QStringList& list) +{ + ui->cbox_effectTypes->clear(); + ui->cbox_effectTypes->addItems(list); +} + +void MainWindow::onDeviceSelected(const QString& id) +{ + ui->cbox_effectSlots->clear(); + m_activeDevice = m_prober->openDeviceByID(id); + + if (m_activeDevice == nullptr) + return; + + fillEffectSlotsList(m_activeDevice->maxEffectCount()); + fillEffectTypesList(m_activeDevice->availableEffectsList()); + m_periodicEffSet->fillAvailableWaveformsList(m_activeDevice->availableWaveformsList()); +} + +void MainWindow::onEffectSlotSelected(const int idx) +{ + if (idx < 0) + return; + if (m_activeDevice == nullptr) + return; + + FFBEffectTypes type = m_activeDevice->effectTypeByEffectIdx(idx); + qDebug() << static_cast(type); + if (type == FFBEffectTypes::NONE) { + qDebug() << "Empty effect"; + ui->cbox_effectTypes->setCurrentIndex(0); + ui->qstw_effectSpecifics->setCurrentWidget(effectSettingsByType(m_activeDevice->effectTypeFromSelectionIdx(0))); + setEffectStatusText(FFBEffect::FFBEffectStatus::NOT_LOADED); + return; + } + + const std::shared_ptr params = m_activeDevice->effectParameters(idx); + /* Set global parameters */ + ui->qle_direction->setText(QString::number(params->direction)); + ui->qle_replayDelay->setText(QString::number(params->replayDelay)); + ui->qle_replayLength->setText(QString::number(params->replayLength)); + + EffectSettings* efs = effectSettingsByType(type); + if (!efs->fillFromParameters(params)) + QMessageBox::warning(this, "UI error", "Unable to read effect parameters."); + ui->cbox_effectTypes->setCurrentIndex(m_activeDevice->effectTypeToIdx(type)); + ui->qstw_effectSpecifics->setCurrentWidget(efs); + setEffectStatusText(m_activeDevice->effectStatusByIdx(idx)); +} + +void MainWindow::onEffectTypeSelected(const int idx) +{ + if (idx < 0) + return; + if (m_activeDevice == nullptr) + return; + + ui->qstw_effectSpecifics->setCurrentWidget(effectSettingsByType(m_activeDevice->effectTypeFromSelectionIdx(idx))); +} + +void MainWindow::onRefreshDevicesClicked() +{ + fillDeviceList(); +} + +void MainWindow::onStartEffectClicked() +{ + if (m_activeDevice == nullptr) + return; + + FFBEffectTypes type = m_activeDevice->effectTypeFromSelectionIdx(ui->cbox_effectTypes->currentIndex()); + std::shared_ptr params; + int effectSlot = ui->cbox_effectSlots->currentIndex(); + if (!readEffectParameters(params, type)) { + qDebug() << "Cannot read effect params."; + return; + } + bool ret = m_activeDevice->startEffect(effectSlot, type, params); + if (ret) + setEffectStatusText(m_activeDevice->effectStatusByIdx(effectSlot)); +} + +void MainWindow::onStopEffectClicked() +{ + if (m_activeDevice == nullptr) + return; + + int effectSlot = ui->cbox_effectSlots->currentIndex(); + m_activeDevice->stopEffect(effectSlot); + setEffectStatusText(m_activeDevice->effectStatusByIdx(effectSlot)); +} + +bool MainWindow::readEnvelopeParameters(std::shared_ptr params, const EnvelopeSettings* es) +{ + if (!params->attackLengthFromString(es->attackLength())) { + QMessageBox::warning(this, res_inputFormatErrCap, "Invalid data in field \"Attack length\""); + return false; + } + if (!params->attackLevelFromString(es->attackLevel())) { + QMessageBox::warning(this, res_inputFormatErrCap, "Invalid data in field \"Attack level\""); + return false; + } + if (!params->fadeLengthFromString(es->fadeLength())) { + QMessageBox::warning(this, res_inputFormatErrCap, "Invalid data in field \"Fade length\""); + return false; + } + if (!params->fadeLevelFromString(es->fadeLevel())) { + QMessageBox::warning(this, res_inputFormatErrCap, "Invalid data in field \"Fade level\""); + return false; + } + + return true; +} + +bool MainWindow::readEffectParameters(std::shared_ptr& params, FFBEffectTypes type) +{ + switch (type) { + case FFBEffectTypes::CONSTANT: + { + std::shared_ptr iparams(new FFBConstantEffectParameters); + if (!readGeneralEffectParameters(iparams)) { + return false; + } + if (!readEnvelopeParameters(iparams, m_constantEffSet->envelopeSettings())) { + return false; + } + if (!iparams->levelFromString(m_constantEffSet->level())) { + QMessageBox::warning(this, res_inputFormatErrCap, "Invalid data in field \"level\""); + return false; + } + + params = iparams; + break; + } + case FFBEffectTypes::PERIODIC: + { + std::shared_ptr iparams(new FFBPeriodicEffectParameters); + if (!readGeneralEffectParameters(iparams)) { + return false; + } + if (!readEnvelopeParameters(iparams, m_periodicEffSet->envelopeSettings())) { + return false; + } + if (!iparams->magnitudeFromString(m_periodicEffSet->magnitude())) { + QMessageBox::warning(this, res_inputFormatErrCap, "Invalid data in field \"Magnitude\""); + return false ; + } + if (!iparams->offsetFromString(m_periodicEffSet->offset())) { + QMessageBox::warning(this, res_inputFormatErrCap, "Invalid data in field \"Offset\""); + return false; + } + if (!iparams->periodFromString(m_periodicEffSet->period())) { + QMessageBox::warning(this, res_inputFormatErrCap, "Invalid data in field \"Period\""); + return false; + } + if (!iparams->phaseFromString(m_periodicEffSet->phase())) { + QMessageBox::warning(this, res_inputFormatErrCap, "Invalid data in field \"Phase\""); + return false; + } + + PeriodicWaveforms waveform = m_activeDevice->waveformByIdx(m_periodicEffSet->waveformIdx()); + iparams->waveformFromIdx(waveform); + + params = iparams; + break; + } + default: + qDebug() << "Unhandled type of effect"; + return false; + } + + return true; +} + +bool MainWindow::readGeneralEffectParameters(std::shared_ptr params) +{ + if (!params->directionFromString(ui->qle_direction->text())) { + QMessageBox::warning(this, res_inputFormatErrCap, "Invalid data in field \"direction\""); + return false; + } + if (!params->replayDelayFromString(ui->qle_replayDelay->text())) { + QMessageBox::warning(this, res_inputFormatErrCap, "Invalid data in field \"Replay delay\""); + return false; + } + if (!params->replayLengthFromString(ui->qle_replayLength->text())) { + QMessageBox::warning(this, res_inputFormatErrCap, "Invalid data in field \"Replay length\""); + return false; + } + + return true; +} + +void MainWindow::setEffectStatusText(const FFBEffect::FFBEffectStatus status) +{ + switch (status) { + case FFBEffect::FFBEffectStatus::NOT_LOADED: + ui->ql_effectStatus->setText(res_effectNotLoaded); + break; + case FFBEffect::FFBEffectStatus::PLAYING: + ui->ql_effectStatus->setText(res_effectPlaying); + break; + case FFBEffect::FFBEffectStatus::STOPPED: + ui->ql_effectStatus->setText(res_effectStopped); + break; + } +} + +MainWindow::~MainWindow() +{ + delete ui; +} diff --git a/mainwindow.h b/mainwindow.h new file mode 100644 index 0000000..7b6e373 --- /dev/null +++ b/mainwindow.h @@ -0,0 +1,55 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include "constanteffectsettings.h" +#include "deviceprober.h" +#include "ffbconstanteffectparameters.h" +#include "ffbdevice.h" +#include "ffbperiodiceffectparameters.h" +#include "periodiceffectsettings.h" +#include +#include + +namespace Ui { + class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(std::shared_ptr prober, QWidget* parent = 0); + ~MainWindow(); + +private: + EffectSettings* effectSettingsByType(FFBEffectTypes type); + void fillDeviceList(); + void fillEffectSlotsList(const int idx); + void fillEffectTypesList(const QStringList& list); + bool readEnvelopeParameters(std::shared_ptr params, const EnvelopeSettings* es); + bool readEffectParameters(std::shared_ptr& params, FFBEffectTypes type); + bool readGeneralEffectParameters(std::shared_ptr params); + void setEffectStatusText(const FFBEffect::FFBEffectStatus status); + + std::shared_ptr m_activeDevice; + ConstantEffectSettings* m_constantEffSet; + PeriodicEffectSettings* m_periodicEffSet; + std::shared_ptr m_prober; + Ui::MainWindow* ui; + + static const QString res_effectNotLoaded; + static const QString res_effectPlaying; + static const QString res_effectStopped; + static const QString res_inputFormatErrCap; + +private slots: + void onDeviceSelected(const QString& id); + void onEffectSlotSelected(const int idx); + void onEffectTypeSelected(const int idx); + void onRefreshDevicesClicked(); + void onStartEffectClicked(); + void onStopEffectClicked(); +}; + +#endif // MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui new file mode 100644 index 0000000..2d8b1e9 --- /dev/null +++ b/mainwindow.ui @@ -0,0 +1,198 @@ + + + MainWindow + + + + 0 + 0 + 444 + 500 + + + + MainWindow + + + + + + + + + + + Devices: + + + + + + + + + + Effect slots: + + + + + + + + + + Refresh + + + + + + + + + + + + 0 + 0 + + + + Effect parameters + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + + + + + + 12 + 75 + true + + + + Not loaded + + + Qt::AlignCenter + + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Direction: + + + + + + + + + + + + Qt::Horizontal + + + + + + + + + Replay length: + + + + + + + + + + Replay delay: + + + + + + + + + + + + Qt::Horizontal + + + + + + + + + Type: + + + + + + + + + + + + + + + + + + + + Start + + + + + + + Stop + + + + + + + Remove + + + + + + + + + + + diff --git a/periodiceffectsettings.cpp b/periodiceffectsettings.cpp new file mode 100644 index 0000000..4637200 --- /dev/null +++ b/periodiceffectsettings.cpp @@ -0,0 +1,66 @@ +#include "periodiceffectsettings.h" +#include "ui_periodiceffectsettings.h" + +PeriodicEffectSettings::PeriodicEffectSettings(QWidget* parent) : + EffectSettings(parent), + ui(new Ui::PeriodicEffectSettings) +{ + ui->setupUi(this); +} + +const EnvelopeSettings* PeriodicEffectSettings::envelopeSettings() const +{ + return ui->qwid_envelope; +} + +void PeriodicEffectSettings::fillAvailableWaveformsList(const QStringList& list) +{ + ui->cbox_waveform->addItems(list); +} + +bool PeriodicEffectSettings::fillFromParameters(const std::shared_ptr params) +{ + try { + const std::shared_ptr pParams = std::dynamic_pointer_cast(params); + + ui->qle_magnitude->setText(QString::number(pParams->magnitude)); + ui->qle_offset->setText(QString::number(pParams->offset)); + ui->qle_period->setText(QString::number(pParams->period)); + ui->qle_phase->setText(QString::number(pParams->phase)); + return ui->qwid_envelope->fillFromParameters(pParams); + } catch (std::bad_cast& ex) { + qCritical(ex.what()); + return false; + } + return false; +} + +QString PeriodicEffectSettings::magnitude() const +{ + return ui->qle_magnitude->text(); +} + +QString PeriodicEffectSettings::offset() const +{ + return ui->qle_offset->text(); +} + +QString PeriodicEffectSettings::period() const +{ + return ui->qle_period->text(); +} + +QString PeriodicEffectSettings::phase() const +{ + return ui->qle_phase->text(); +} + +int PeriodicEffectSettings::waveformIdx() const +{ + return ui->cbox_waveform->currentIndex(); +} + +PeriodicEffectSettings::~PeriodicEffectSettings() +{ + delete ui; +} diff --git a/periodiceffectsettings.h b/periodiceffectsettings.h new file mode 100644 index 0000000..47a4ee3 --- /dev/null +++ b/periodiceffectsettings.h @@ -0,0 +1,32 @@ +#ifndef PERIODICEFFECTSETTINGS_H +#define PERIODICEFFECTSETTINGS_H + +#include "effectsettings.h" +#include "envelopesettings.h" +#include "ffbperiodiceffectparameters.h" + +namespace Ui { + class PeriodicEffectSettings; +} + +class PeriodicEffectSettings : public EffectSettings +{ + Q_OBJECT + +public: + explicit PeriodicEffectSettings(QWidget* parent = 0); + ~PeriodicEffectSettings(); + const EnvelopeSettings* envelopeSettings() const; + void fillAvailableWaveformsList(const QStringList& list); + bool fillFromParameters(const std::shared_ptr params); + QString magnitude() const; + QString offset() const; + QString period() const; + QString phase() const; + int waveformIdx() const; + +private: + Ui::PeriodicEffectSettings* ui; +}; + +#endif // PERIODICEFFECTSETTINGS_H diff --git a/periodiceffectsettings.ui b/periodiceffectsettings.ui new file mode 100644 index 0000000..4ad825b --- /dev/null +++ b/periodiceffectsettings.ui @@ -0,0 +1,138 @@ + + + PeriodicEffectSettings + + + + 0 + 0 + 400 + 300 + + + + + 0 + 0 + + + + + 400 + 300 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + Waveform: + + + + + + + + + + Period: + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Magnitude: + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Phase: + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Offset: + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + + EnvelopeSettings + QWidget +
envelopesettings.h
+ 1 +
+
+ + +