virtual void close() = 0;
virtual bool queryDeviceCapabilities() = 0;
virtual bool removeAndEraseEffect(const int idx) = 0;
+ virtual bool setGain(const int gain) = 0;
virtual bool startEffect(const int idx, const FFBEffectTypes type, std::shared_ptr<FFBEffectParameters> parameters) = 0;
virtual bool stopEffect(const int idx) = 0;
virtual bool uploadEffect(const int idx, const FFBEffectTypes type, std::shared_ptr<FFBEffectParameters> parameters) = 0;
std::vector<ConditionSubtypes> m_availableConditionSubtypes;
std::vector<FFBEffectTypes> m_availableEffects;
std::vector<PeriodicWaveforms> m_availablePeriodicWaveforms;
+ bool m_adjustableGain;
+
std::vector<std::shared_ptr<FFBEffect>> m_effects;
const int c_maxEffectCount;
#define CHECK_EFFECT_IDX(idx) if (idx < 0 || idx > c_maxEffectCount) return false
const quint8 LinuxFFBDevice::BITS_PER_LONG = sizeof(unsigned long) * 8;
+const QString LinuxFFBDevice::LNXDEV_ERR_CAPTION("Linux FFB device");
LinuxFFBDevice::LinuxFFBDevice(const int fd, const int maxEffectCount, const QString path) :
FFBDevice(maxEffectCount),
if (testBit(FF_INERTIA, caps))
m_availableConditionSubtypes.push_back(ConditionSubtypes::INERTIA);
+ /* Query device-wide capabilities */
+ if (testBit(FF_GAIN, caps))
+ m_adjustableGain = true;
+
return true;
}
return true;
}
+bool LinuxFFBDevice::setGain(const int gain)
+{
+ struct input_event evt;
+ int ret;
+
+ if (!m_adjustableGain) {
+ QMessageBox::warning(nullptr, LNXDEV_ERR_CAPTION, "Device does not have adjustable gain");
+ return false;
+ }
+
+ if (gain < 0 || gain > 0xFFFF) {
+ QMessageBox::warning(nullptr, LNXDEV_ERR_CAPTION, "Gain must be within <0; 65535>");
+ return false;
+ }
+
+ evt.type = EV_FF;
+ evt.code = FF_GAIN;
+ evt.value = gain;
+
+ ret = write(c_fd, &evt, sizeof(struct input_event));
+ if (ret != sizeof(struct input_event)) {
+ QMessageBox::warning(nullptr, LNXDEV_ERR_CAPTION, QString("Unable to set gain"));
+ return false;
+ }
+
+ return true;
+}
+
+
bool LinuxFFBDevice::startEffect(const int idx, const FFBEffectTypes type, std::shared_ptr<FFBEffectParameters> parameters)
{
std::shared_ptr<LinuxFFBEffect> linEff;
ret = write(c_fd, &evt, sizeof(struct input_event));
if (ret != sizeof(struct input_event)) {
- QMessageBox::critical(nullptr, "FFB Device", "Effect could not have been started, error code: " + QString::number(ret));
+ QMessageBox::critical(nullptr, LNXDEV_ERR_CAPTION, "Effect could not have been started, error code: " + QString::number(ret));
qDebug() << "Effect not started" << ret;
return false;
}
if (*m_effects[idx] != *linEff) {
if (!removeEffect(idx)) {
- QMessageBox::critical(nullptr, "FFB Device", "Unable to remove effect");
+ QMessageBox::critical(nullptr, LNXDEV_ERR_CAPTION, "Unable to remove effect");
return false;
}
linEff->setStatus(FFBEffect::FFBEffectStatus::UPLOADED);
kernelEff = linEff->createFFStruct();
if (kernelEff == nullptr) {
- QMessageBox::critical(nullptr, "FFB Device", "ff_effect struct could not have been created. Effect not uploaded.");
+ QMessageBox::critical(nullptr, LNXDEV_ERR_CAPTION, "ff_effect struct could not have been created. Effect not uploaded.");
qDebug() << "struct ff_effect not created";
return false;
}
int ret = ioctl(c_fd, EVIOCSFF, kernelEff);
if (ret < 0) {
- QMessageBox::critical(nullptr, "FFB Device", "Effect could not have been uploaded, error code: " + QString::number(ret));
+ QMessageBox::critical(nullptr, LNXDEV_ERR_CAPTION, "Effect could not have been uploaded, error code: " + QString::number(ret));
qDebug() << "Effect not uploaded" << ret;
delete kernelEff;
return false;
void close();
bool queryDeviceCapabilities();
bool removeAndEraseEffect(const int idx);
+ bool setGain(const int gain);
bool startEffect(const int idx, const FFBEffectTypes type, std::shared_ptr<FFBEffectParameters> parameters);
bool stopEffect(const int idx);
bool uploadEffect(const int idx, const FFBEffectTypes type, std::shared_ptr<FFBEffectParameters> parameters);
static inline bool testBit(unsigned long bit, unsigned long* array) { return (array[longIdx(bit)] >> offset(bit)) & 1; }
static const quint8 BITS_PER_LONG;
+ static const QString LNXDEV_ERR_CAPTION;
+
signals:
public slots:
connect(ui->cbox_interfaces, SIGNAL(activated(int)), this, SLOT(onInterfaceSelected(const int)));
connect(ui->qpb_refreshDevices, SIGNAL(clicked()), this, SLOT(onRefreshDevicesClicked()));
connect(ui->qpb_remove, SIGNAL(clicked()), this, SLOT(onRemoveEffectClicked()));
+ connect(ui->qpb_setGain, SIGNAL(clicked()), this, SLOT(onSetGainClicked()));
connect(ui->qpb_start, SIGNAL(clicked()), this, SLOT(onStartEffectClicked()));
connect(ui->qpb_stop, SIGNAL(clicked()), this, SLOT(onStopEffectClicked()));
connect(ui->qpb_upload, SIGNAL(clicked()), this, SLOT(onUploadEffectClicked()));
setEffectStatusText(m_activeDevice->effectStatusByIdx(effectIdx));
}
+void MainWindow::onSetGainClicked()
+{
+ bool ok;
+ int gain;
+
+ if (m_activeDevice == nullptr)
+ return;
+
+ gain = ui->qle_gain->text().toInt(&ok);
+ if (!ok) {
+ QMessageBox::warning(this, res_inputFormatErrCap, "Invalid gain value");
+ return;
+ }
+
+ m_activeDevice->setGain(gain);
+}
+
void MainWindow::onStartEffectClicked()
{
bool ok;
void onInterfaceSelected(const int cboxIdx);
void onRefreshDevicesClicked();
void onRemoveEffectClicked();
+ void onSetGainClicked();
void onStartEffectClicked();
void onStopEffectClicked();
void onUploadEffectClicked();
<x>0</x>
<y>0</y>
<width>444</width>
- <height>546</height>
+ <height>623</height>
</rect>
</property>
<property name="windowTitle">
</item>
</layout>
</item>
+ <item>
+ <widget class="Line" name="line_4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="ql_deviceSettings">
+ <property name="text">
+ <string>Device settings</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="qle_gain">
+ <property name="text">
+ <string>65535</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="ql_gain">
+ <property name="text">
+ <string>Gain:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QPushButton" name="qpb_setGain">
+ <property name="text">
+ <string>Set gain</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
<item>
<widget class="QLabel" name="ql_effectParameters">
<property name="sizePolicy">
#define CHECK_EFFECT_IDX(idx) if (idx < 0 || idx > c_maxEffectCount) return false
+const QString SDL2FFBDevice::SDL2DEV_ERR_CAPTION("SDL2 device error");
+
SDL2FFBDevice::SDL2FFBDevice(SDL_Haptic* haptic, const int maxEffectCount) :
FFBDevice(maxEffectCount),
c_haptic(haptic)
if (hasCondition)
m_availableEffects.push_back(FFBEffectTypes::CONDITION);
+ if (caps & SDL_HAPTIC_GAIN)
+ m_adjustableGain = true;
+
return true;
}
return true;
}
+bool SDL2FFBDevice::setGain(const int gain)
+{
+ if (!m_adjustableGain) {
+ QMessageBox::warning(nullptr, SDL2DEV_ERR_CAPTION, "Device does not have adjustable gain");
+ return false;
+ }
+
+ if (gain < 0 || gain > 100) {
+ QMessageBox::warning(nullptr, SDL2DEV_ERR_CAPTION, "Gain must be within <0; 100>");
+ return false;
+ }
+
+ if (SDL_HapticSetGain(c_haptic, gain) < 0) {
+ QMessageBox::warning(nullptr, SDL2DEV_ERR_CAPTION, QString("Unable to set gain:\n%1").arg(SDL_GetError()));
+ return false;
+ }
+
+ return true;
+}
+
bool SDL2FFBDevice::startEffect(const int idx, const FFBEffectTypes type, std::shared_ptr<FFBEffectParameters> parameters)
{
std::shared_ptr<SDL2FFBEffect> sdlEff;
repeat = sdlEff->parameters()->repeat;
if (SDL_HapticRunEffect(c_haptic, sdlEff->internalIdx(), repeat) < 0) {
- QMessageBox::warning(nullptr, "SDL2 error", QString("Unable to start the effect:\n%1").arg(SDL_GetError()));
+ QMessageBox::warning(nullptr, SDL2DEV_ERR_CAPTION, QString("Unable to start the effect:\n%1").arg(SDL_GetError()));
return false;
}
sdlEff = std::static_pointer_cast<SDL2FFBEffect>(m_effects[idx]);
if (SDL_HapticStopEffect(c_haptic, sdlEff->internalIdx()) < 0) {
- QMessageBox::critical(nullptr, "SDL2 error", QString("Unable to stop the effect:\n%1").arg(SDL_GetError()));
+ QMessageBox::critical(nullptr, SDL2DEV_ERR_CAPTION, QString("Unable to stop the effect:\n%1").arg(SDL_GetError()));
return false;
}
intIdx = SDL_HapticUpdateEffect(c_haptic, std::static_pointer_cast<SDL2FFBEffect>(m_effects[idx])->internalIdx(), underlEff);
if (intIdx < 0) {
- QMessageBox::critical(nullptr, "SDL2 error", QString("Unable to update the effect:\n%1").arg(SDL_GetError()));
+ QMessageBox::critical(nullptr, SDL2DEV_ERR_CAPTION, QString("Unable to update the effect:\n%1").arg(SDL_GetError()));
m_effects[idx]->setStatus(FFBEffect::FFBEffectStatus::UPLOADED);
return true;
}
intIdx = SDL_HapticNewEffect(c_haptic, underlEff);
if (intIdx < 0) {
- QMessageBox::critical(nullptr, "SDL2 error", QString("Unable to create effect:\n%1").arg(SDL_GetError()));
+ QMessageBox::critical(nullptr, SDL2DEV_ERR_CAPTION, QString("Unable to create effect:\n%1").arg(SDL_GetError()));
return false;
}
sdlEff->setStatus(FFBEffect::FFBEffectStatus::UPLOADED);
void close();
bool queryDeviceCapabilities();
bool removeAndEraseEffect(const int idx);
+ bool setGain(const int idx);
bool startEffect(const int idx, const FFBEffectTypes type, std::shared_ptr<FFBEffectParameters> parameters);
bool stopEffect(const int idx);
bool uploadEffect(const int idx, const FFBEffectTypes type, std::shared_ptr<FFBEffectParameters> parameters);
bool removeEffect(const int idx);
SDL_Haptic* c_haptic;
+
+ static const QString SDL2DEV_ERR_CAPTION;
};
#endif // SDL2FFBDEVICE_H
\ No newline at end of file