find_package(Qt5Widgets)
+include(FindPkgConfig)
+pkg_search_module(SDL2 sdl2)
+if (SDL2_FOUND)
+ add_definitions("-DFFBC_HAVE_SDL2")
+endif()
+
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(FFBChecker_SRCS
conditioneffectsettings.cpp
constanteffectsettings.cpp
- deviceprober.cpp
effectsettings.cpp
envelopesettings.cpp
ffbconditioneffect.cpp
ffbrumbleeffect.cpp
ffbrumbleeffectparameters.cpp
globalsettings.cpp
+ linuxdeviceprober.cpp
+ linuxffbdevice.cpp
main.cpp
mainwindow.cpp
periodiceffectsettings.cpp
rampeffectsettings.cpp
rumbleeffectsettings.cpp)
-include_directories(
- ${CMAKE_CURRENT_SOURCE_DIR})
+include_directories(${CMAKE_CURRENT_SOURCE_DIR})
+if (SDL2_FOUND)
+ include_directories(${SDL2_INCLUDE_DIRS})
+endif()
add_executable(FFBChecker ${FFBChecker_SRCS})
target_link_libraries(FFBChecker Qt5::Widgets)
+
+if (SDL2_FOUND)
+ target_link_libraries(FFBChecker ${SDL2_LIBRARIES})
+endif()
#include "ffbdevice.h"
#include <memory>
-#include <QtCore/QDir>
+#include <QtCore/QVariant>
#include <QtCore/QObject>
-class DeviceProber : public QObject
+class DeviceProber
{
- Q_OBJECT
public:
struct DeviceInfo {
- QString path;
+ QVariant id;
QString name;
};
typedef QList<DeviceInfo> DeviceList;
- explicit DeviceProber(QObject* parent = 0);
- DeviceList listDevices();
- std::shared_ptr<FFBDevice> openDevice(const QString& path);
-
-private:
- std::list<std::shared_ptr<FFBDevice>> m_openedDevices;
-
- static const QString DEVICE_NODES_PATH;
- static const QString res_ffbdeviceErrCap;
+ enum class DeviceInterfaces : unsigned int {
+ NONE,
+ LINUX,
+ SDL2
+ };
-signals:
+ virtual void closeAllDevices() = 0;
+ virtual DeviceList listDevices() = 0;
+ virtual std::shared_ptr<FFBDevice> openDevice(const QString& id) = 0;
-public slots:
+ const DeviceInterfaces type;
+protected:
+ explicit DeviceProber() : type(DeviceInterfaces::NONE) {}
};
+
#endif // DEVICEPROBER_H
+
#include "ffbdevice.h"
#include "ffbeffectfactory.h"
-#include <QtWidgets/QMessageBox>
-#include <QDebug>
-
-#define CHECK_EFFECT_IDX(idx) if (idx < 0 || idx > c_maxEffectCount) return false
-
-const quint8 FFBDevice::BITS_PER_LONG = sizeof(unsigned long) * 8;
-
-FFBDevice::FFBDevice(const int fd, const QString& path, const int maxEffectCount, QObject* parent) :
- QObject(parent),
- c_fd(fd),
- c_maxEffectCount(maxEffectCount),
- c_path(path)
-{
- for (int i = 0; i < maxEffectCount; i++)
- m_effects.push_back(FFBEffectFactory::createEffect(FFBEffectTypes::NONE));
-}
const std::vector<ConditionSubtypes>& FFBDevice::availableConditionSubtypesList() const
{
const std::shared_ptr<FFBEffectParameters> FFBDevice::effectParameters(const int idx)
{
- if (idx >= c_maxEffectCount)
+ if (idx >= c_maxEffectCount || idx < 0)
+ return nullptr;
+
+ if (m_effects[idx] == nullptr)
return nullptr;
return m_effects[idx]->parameters();
FFBEffect::FFBEffectStatus FFBDevice::effectStatusByIdx(const int idx) const
{
+ if (idx >= c_maxEffectCount || idx < 0)
+ return FFBEffect::FFBEffectStatus::NOT_LOADED;
+
if (m_effects[idx] == nullptr)
return FFBEffect::FFBEffectStatus::NOT_LOADED;
FFBEffectTypes FFBDevice::effectTypeByEffectIdx(const int idx) const
{
+ if (idx >= c_maxEffectCount || idx < 0)
+ return FFBEffectTypes::NONE;
+
+ if (m_effects[idx] == nullptr)
+ return FFBEffectTypes::NONE;
+
return m_effects[idx]->type();
}
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) || testBit(FF_FRICTION, caps) ||
- testBit(FF_DAMPER, caps) || testBit(FF_INERTIA, caps))
- m_availableEffects.push_back(FFBEffectTypes::CONDITION);
- if (testBit(FF_RUMBLE, caps))
- m_availableEffects.push_back(FFBEffectTypes::RUMBLE);
-
- /* 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);
- }
-
- /* Query condition effect subtypes */
- if (testBit(FF_SPRING, caps))
- m_availableConditionSubtypes.push_back(ConditionSubtypes::SPRING);
- if (testBit(FF_FRICTION, caps))
- m_availableConditionSubtypes.push_back(ConditionSubtypes::FRICTION);
- if (testBit(FF_DAMPER, caps))
- m_availableConditionSubtypes.push_back(ConditionSubtypes::DAMPER);
- if (testBit(FF_INERTIA, caps))
- m_availableConditionSubtypes.push_back(ConditionSubtypes::INERTIA);
-
- return true;
-}
-
-bool FFBDevice::removeAndEraseEffect(const int idx)
-{
- if (m_effects[idx]->status() == FFBEffect::FFBEffectStatus::NOT_LOADED)
- return true;
-
- if (removeEffect(idx)) {
- m_effects[idx] = FFBEffectFactory::createEffect(FFBEffectTypes::NONE);
- if (m_effects[idx]->type() != FFBEffectTypes::NONE) {
- qCritical("Unable to empty the effect slot.");
- return false;
- }
- } else {
- qCritical("Unable to stop the effect.");
- return false;
- }
-
- m_effects[idx]->setStatus(FFBEffect::FFBEffectStatus::NOT_LOADED);
- return true;
-}
-
-bool FFBDevice::removeEffect(const int idx)
-{
- if (!stopEffect(idx))
- return false;
-
- int internalIdx = m_effects[idx]->internalIdx();
- int ret = ioctl(c_fd, EVIOCRMFF, internalIdx);
- if (ret < 0)
- return false;
- return true;
-}
-
-bool FFBDevice::startEffect(const int idx, const FFBEffectTypes type, std::shared_ptr<FFBEffectParameters> parameters)
-{
- int ret;
-
- CHECK_EFFECT_IDX(idx);
-
- if (m_effects[idx]->status() == FFBEffect::FFBEffectStatus::NOT_LOADED) {
- if (!uploadEffect(idx, type, parameters))
- return false;
- }
- if (m_effects[idx]->status() == FFBEffect::FFBEffectStatus::PLAYING)
- return true;
-
- /* Start playback */
- struct input_event evt;
- evt.type = EV_FF;
- evt.code = m_effects[idx]->internalIdx();
- evt.value = m_effects[idx]->parameters()->repeat;
-
- 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));
- qDebug() << "Effect not started" << ret;
- return false;
- }
-
- m_effects[idx]->setStatus(FFBEffect::FFBEffectStatus::PLAYING);
-
- return true;
-}
-
-bool FFBDevice::stopEffect(const int idx)
-{
- CHECK_EFFECT_IDX(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::UPLOADED);
- return true;
-}
-
-bool FFBDevice::uploadEffect(const int idx, const FFBEffectTypes type, std::shared_ptr<FFBEffectParameters> parameters)
-{
- struct ff_effect* kernelEff = nullptr;
- std::shared_ptr<FFBEffect> effect = FFBEffectFactory::createEffect(type);
-
- CHECK_EFFECT_IDX(idx);
-
- if (effect == nullptr) {
- qDebug() << "Unable to create effect";
- return false;
- }
- if (!effect->setParameters(parameters)) {
- qDebug() << "Unable to set effect parameters, some values are probably invalid.";
- return false;
- }
-
- if (idx < 0 || idx > c_maxEffectCount) {
- qCritical() << "Effect index out of bounds";
- return false;
- }
-
- /* There is no effect in the selected slot */
- if (m_effects[idx]->type() == FFBEffectTypes::NONE) {
- effect->setStatus(FFBEffect::FFBEffectStatus::UPLOADED);
- qDebug() << "Creating new effect";
- } else {
- if (*m_effects[idx] != *effect) {
- if (!removeEffect(idx)) {
- QMessageBox::critical(nullptr, "FFB Device", "Unable to remove effect");
- return false;
- }
- effect->setStatus(FFBEffect::FFBEffectStatus::UPLOADED);
- qDebug() << "Recreating effect" << idx;
- } else {
- effect->setInternalIdx(m_effects[idx]->internalIdx());
- effect->setStatus(m_effects[idx]->status());
- qDebug() << "Updating effect" << idx;
- }
- }
-
- kernelEff = effect->createFFStruct();
- if (kernelEff == nullptr) {
- QMessageBox::critical(nullptr, "FFB Device", "ff_effect struct could not have been created. Effect not uploaded.");
- qDebug() << "struct ff_effect not created";
- return false;
- }
-
- qDebug() << kernelEff->u.condition[0].center << kernelEff->u.condition[0].deadband << kernelEff->u.condition[1].center << kernelEff->u.condition[1].deadband;
-
- 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));
- qDebug() << "Effect not uploaded" << ret;
- delete kernelEff;
- return false;
- }
-
- effect->setInternalIdx(kernelEff->id);
- delete kernelEff;
-
- m_effects[idx] = effect;
- return true;
-}
#include "ffbeffect.h"
#include <memory>
#include <vector>
-#include <QtCore/QObject>
-#include <QtCore/QStringList>
-#include <fcntl.h>
-#include <unistd.h>
-#include <linux/input.h>
-#include <sys/stat.h>
-
-class FFBDevice : public QObject
-{
- Q_OBJECT
-public:
- explicit FFBDevice(const int fd, const QString& path, const int maxEffectCount, QObject* parent = 0);
+class FFBDevice {
+public:
const std::vector<ConditionSubtypes>& availableConditionSubtypesList() const;
const std::vector<FFBEffectTypes>& availableEffectsList() const;
const std::vector<PeriodicWaveforms>& availableWaveformsList() const;
bool hasEffect(FFBEffectTypes id) const;
bool hasPeriodicWaveform(PeriodicWaveforms id) const;
inline int maxEffectCount() const { return c_maxEffectCount; }
- inline const QString& path() const { return c_path; }
- bool queryDeviceCapabilities();
- bool removeAndEraseEffect(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);
inline PeriodicWaveforms waveformByIdx(const int idx) const { return m_availablePeriodicWaveforms[idx]; }
-private:
- bool removeEffect(const int idx);
+ virtual void close() = 0;
+ virtual bool queryDeviceCapabilities() = 0;
+ virtual bool removeAndEraseEffect(const int idx) = 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;
+
+protected:
+ explicit FFBDevice(const int maxEffectCount) :
+ c_maxEffectCount(maxEffectCount) {}
+
std::vector<ConditionSubtypes> m_availableConditionSubtypes;
std::vector<FFBEffectTypes> m_availableEffects;
std::vector<PeriodicWaveforms> m_availablePeriodicWaveforms;
std::vector<std::shared_ptr<FFBEffect>> m_effects;
- const int c_fd;
const int c_maxEffectCount;
- const QString c_path;
-
- 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:
};
-#include "deviceprober.h"
-#include "ffbdevice.h"
+#include "linuxdeviceprober.h"
#include <QtCore/QDebug>
+#include <QtCore/QDir>
#include <QtWidgets/QMessageBox>
#include <linux/input.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
-const QString DeviceProber::DEVICE_NODES_PATH("/dev/input");
-const QString DeviceProber::res_ffbdeviceErrCap("FFB Device error");
+const QString LinuxDeviceProber::DEVICE_NODES_PATH("/dev/input");
+const QString LinuxDeviceProber::res_ffbdeviceErrCap("FFB Device error");
-DeviceProber::DeviceProber(QObject* parent) :
- QObject(parent)
+void LinuxDeviceProber::closeAllDevices()
{
+ for (std::shared_ptr<LinuxFFBDevice> dev : m_openedDevices) {
+ dev->close();
+ }
}
-DeviceProber::DeviceList DeviceProber::listDevices()
+DeviceProber::DeviceList LinuxDeviceProber::listDevices()
{
DeviceProber::DeviceList list;
char deviceName[64];
continue;
}
- dinfo.path = devicePath;
+ dinfo.id = devicePath;
ret = ioctl(fd, EVIOCGNAME(63), deviceName);
if (ret < 0)
qDebug() << "Cannot get name of device" << d << strerror(errno);
return list;
}
-std::shared_ptr<FFBDevice> DeviceProber::openDevice(const QString& path)
+std::shared_ptr<FFBDevice> LinuxDeviceProber::openDevice(const QString& id)
{
/* Check if the device is already opened */
- for (std::shared_ptr<FFBDevice> dev : m_openedDevices) {
- if (QString::compare(path, dev->path()) == 0) {
- qDebug() << "Device" << path << "already opened";
+ for (std::shared_ptr<LinuxFFBDevice>& dev : m_openedDevices) {
+ if (QString::compare(id, dev->path()) == 0) {
+ qDebug() << "Device" << id << "already opened";
return dev;
}
}
- int fd = open(path.toLocal8Bit(), O_RDWR);
+ int fd = open(id.toLocal8Bit(), O_RDWR);
if (!fd) {
QMessageBox::critical(nullptr, res_ffbdeviceErrCap, "Cannot open device.");
return nullptr;
return nullptr;
}
- std::shared_ptr<FFBDevice> device(new FFBDevice(fd, path, maxEffectCount));
+ std::shared_ptr<LinuxFFBDevice> device(new LinuxFFBDevice(fd, maxEffectCount, id));
if (!device->queryDeviceCapabilities()) {
QMessageBox::critical(nullptr, res_ffbdeviceErrCap, "Unable to query device capabilities.");
return nullptr;
--- /dev/null
+#ifndef LINUXDEVICEPROBER_H
+#define LINUXDEVICEPROBER_H
+
+#include "deviceprober.h"
+#include "linuxffbdevice.h"
+
+class LinuxDeviceProber : public DeviceProber
+{
+public:
+ explicit LinuxDeviceProber() {}
+ void closeAllDevices();
+ DeviceList listDevices();
+ std::shared_ptr<FFBDevice> openDevice(const QString& id);
+
+private:
+ std::list<std::shared_ptr<LinuxFFBDevice>> m_openedDevices;
+
+ static const QString DEVICE_NODES_PATH;
+ static const QString res_ffbdeviceErrCap;
+
+signals:
+
+public slots:
+
+};
+
+#endif // LINUXDEVICEPROBER_H
--- /dev/null
+#include "linuxffbdevice.h"
+#include "ffbeffectfactory.h"
+#include <QtWidgets/QMessageBox>
+#include <QDebug>
+#include <fcntl.h>
+#include <unistd.h>
+#include <linux/input.h>
+#include <sys/stat.h>
+
+#define CHECK_EFFECT_IDX(idx) if (idx < 0 || idx > c_maxEffectCount) return false
+
+const quint8 LinuxFFBDevice::BITS_PER_LONG = sizeof(unsigned long) * 8;
+
+LinuxFFBDevice::LinuxFFBDevice(const int fd, const int maxEffectCount, const QString path) :
+ FFBDevice(maxEffectCount),
+ c_fd(fd),
+ c_path(path)
+{
+ for (int i = 0; i < maxEffectCount; i++)
+ m_effects.push_back(FFBEffectFactory::createEffect(FFBEffectTypes::NONE));
+}
+
+void LinuxFFBDevice::close()
+{
+ for (int idx = 0; idx < c_maxEffectCount; idx++) {
+ stopEffect(idx);
+ removeAndEraseEffect(idx);
+ }
+
+ ::close(c_fd);
+}
+
+bool LinuxFFBDevice::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) || testBit(FF_FRICTION, caps) ||
+ testBit(FF_DAMPER, caps) || testBit(FF_INERTIA, caps))
+ m_availableEffects.push_back(FFBEffectTypes::CONDITION);
+ if (testBit(FF_RUMBLE, caps))
+ m_availableEffects.push_back(FFBEffectTypes::RUMBLE);
+
+ /* 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);
+ }
+
+ /* Query condition effect subtypes */
+ if (testBit(FF_SPRING, caps))
+ m_availableConditionSubtypes.push_back(ConditionSubtypes::SPRING);
+ if (testBit(FF_FRICTION, caps))
+ m_availableConditionSubtypes.push_back(ConditionSubtypes::FRICTION);
+ if (testBit(FF_DAMPER, caps))
+ m_availableConditionSubtypes.push_back(ConditionSubtypes::DAMPER);
+ if (testBit(FF_INERTIA, caps))
+ m_availableConditionSubtypes.push_back(ConditionSubtypes::INERTIA);
+
+ return true;
+}
+
+bool LinuxFFBDevice::removeAndEraseEffect(const int idx)
+{
+ if (m_effects[idx]->status() == FFBEffect::FFBEffectStatus::NOT_LOADED)
+ return true;
+
+ if (removeEffect(idx)) {
+ m_effects[idx] = FFBEffectFactory::createEffect(FFBEffectTypes::NONE);
+ if (m_effects[idx]->type() != FFBEffectTypes::NONE) {
+ qCritical("Unable to empty the effect slot.");
+ return false;
+ }
+ } else {
+ qCritical("Unable to stop the effect.");
+ return false;
+ }
+
+ m_effects[idx]->setStatus(FFBEffect::FFBEffectStatus::NOT_LOADED);
+ return true;
+}
+
+bool LinuxFFBDevice::removeEffect(const int idx)
+{
+ if (!stopEffect(idx))
+ return false;
+
+ int internalIdx = m_effects[idx]->internalIdx();
+ int ret = ioctl(c_fd, EVIOCRMFF, internalIdx);
+ if (ret < 0)
+ return false;
+ return true;
+}
+
+bool LinuxFFBDevice::startEffect(const int idx, const FFBEffectTypes type, std::shared_ptr<FFBEffectParameters> parameters)
+{
+ int ret;
+
+ CHECK_EFFECT_IDX(idx);
+
+ if (m_effects[idx]->status() == FFBEffect::FFBEffectStatus::NOT_LOADED) {
+ if (!uploadEffect(idx, type, parameters))
+ return false;
+ }
+ if (m_effects[idx]->status() == FFBEffect::FFBEffectStatus::PLAYING)
+ return true;
+
+ /* Start playback */
+ struct input_event evt;
+ evt.type = EV_FF;
+ evt.code = m_effects[idx]->internalIdx();
+ evt.value = m_effects[idx]->parameters()->repeat;
+
+ 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));
+ qDebug() << "Effect not started" << ret;
+ return false;
+ }
+
+ m_effects[idx]->setStatus(FFBEffect::FFBEffectStatus::PLAYING);
+
+ return true;
+}
+
+bool LinuxFFBDevice::stopEffect(const int idx)
+{
+ CHECK_EFFECT_IDX(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::UPLOADED);
+ return true;
+}
+
+bool LinuxFFBDevice::uploadEffect(const int idx, const FFBEffectTypes type, std::shared_ptr<FFBEffectParameters> parameters)
+{
+ struct ff_effect* kernelEff = nullptr;
+ std::shared_ptr<FFBEffect> effect = FFBEffectFactory::createEffect(type);
+
+ CHECK_EFFECT_IDX(idx);
+
+ if (effect == nullptr) {
+ qDebug() << "Unable to create effect";
+ return false;
+ }
+ if (!effect->setParameters(parameters)) {
+ qDebug() << "Unable to set effect parameters, some values are probably invalid.";
+ return false;
+ }
+
+ if (idx < 0 || idx > c_maxEffectCount) {
+ qCritical() << "Effect index out of bounds";
+ return false;
+ }
+
+ /* There is no effect in the selected slot */
+ if (m_effects[idx]->type() == FFBEffectTypes::NONE) {
+ effect->setStatus(FFBEffect::FFBEffectStatus::UPLOADED);
+ qDebug() << "Creating new effect";
+ } else {
+ if (*m_effects[idx] != *effect) {
+ if (!removeEffect(idx)) {
+ QMessageBox::critical(nullptr, "FFB Device", "Unable to remove effect");
+ return false;
+ }
+ effect->setStatus(FFBEffect::FFBEffectStatus::UPLOADED);
+ qDebug() << "Recreating effect" << idx;
+ } else {
+ effect->setInternalIdx(m_effects[idx]->internalIdx());
+ effect->setStatus(m_effects[idx]->status());
+ qDebug() << "Updating effect" << idx;
+ }
+ }
+
+ kernelEff = effect->createFFStruct();
+ if (kernelEff == nullptr) {
+ QMessageBox::critical(nullptr, "FFB Device", "ff_effect struct could not have been created. Effect not uploaded.");
+ qDebug() << "struct ff_effect not created";
+ return false;
+ }
+
+ qDebug() << kernelEff->u.condition[0].center << kernelEff->u.condition[0].deadband << kernelEff->u.condition[1].center << kernelEff->u.condition[1].deadband;
+
+ 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));
+ qDebug() << "Effect not uploaded" << ret;
+ delete kernelEff;
+ return false;
+ }
+
+ effect->setInternalIdx(kernelEff->id);
+ delete kernelEff;
+
+ m_effects[idx] = effect;
+ return true;
+}
--- /dev/null
+#ifndef LINUXFFBDEVICE_H
+#define LINUXFFBDEVICE_H
+
+#include "ffbdevice.h"
+
+class LinuxFFBDevice : public FFBDevice
+{
+public:
+
+ explicit LinuxFFBDevice(const int fd, const int maxEffectCount, const QString path);
+ const QString& path() const { return c_path; }
+
+ /* Overriden virtual functions */
+ void close();
+ bool queryDeviceCapabilities();
+ bool removeAndEraseEffect(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);
+ inline PeriodicWaveforms waveformByIdx(const int idx) const { return m_availablePeriodicWaveforms[idx]; }
+
+private:
+ bool removeEffect(const int idx);
+
+ const int c_fd;
+ const QString c_path;
+
+ 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
GlobalSettings::init(doSanityChecks);
}
- std::shared_ptr<DeviceProber> prober(new DeviceProber);
- MainWindow* mWin = new MainWindow(prober, VERSION_STRING);
+ MainWindow* mWin = new MainWindow(VERSION_STRING);
mWin->show();
return myApp.exec();
#include "globalsettings.h"
+#include "linuxdeviceprober.h"
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QtWidgets/QMessageBox>
const QString MainWindow::res_effectUploaded("Uploaded");
const QString MainWindow::res_inputFormatErrCap("Invalid input format.");
-MainWindow::MainWindow(std::shared_ptr<DeviceProber> prober, const QString& title, QWidget* parent) :
+MainWindow::MainWindow(const QString& title, QWidget* parent) :
QMainWindow(parent),
- m_prober(prober),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
if (GlobalSettings::GS()->doSanityChecks)
ui->ql_noChecksWarning->setHidden(true);
+ /* Fill the list of available interfaces */
+ ui->cbox_interfaces->addItem("Linux API", static_cast<std::underlying_type<DeviceProber::DeviceInterfaces>::type>(DeviceProber::DeviceInterfaces::LINUX));
+#ifdef FFBC_HAVE_SDL2
+ ui->cbox_interfaces->addItem("SDL2", static_cast<std::underlying_type<DeviceProber::DeviceInterfaces>::type>(DeviceProber::DeviceInterfaces::SDL2));
+#endif
+
+ ui->cbox_interfaces->setCurrentIndex(0);
+ createDeviceProber(DeviceProber::DeviceInterfaces::LINUX);
fillDeviceList();
+
connect(ui->cbox_devices, SIGNAL(activated(const int)), this, SLOT(onDeviceSelected(const int)));
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->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_start, SIGNAL(clicked()), this, SLOT(onStartEffectClicked()));
connect(ui->qpb_upload, SIGNAL(clicked()), this, SLOT(onUploadEffectClicked()));
}
+void MainWindow::createDeviceProber(const DeviceProber::DeviceInterfaces iface)
+{
+ std::shared_ptr<DeviceProber> prober;
+
+ if (m_prober != nullptr)
+ m_prober->closeAllDevices();
+
+ switch (iface) {
+ case DeviceProber::DeviceInterfaces::LINUX:
+ prober = std::make_shared<LinuxDeviceProber>();
+ break;
+ default:
+ QMessageBox::critical(this, "Cannot probe devices", "Selected interface is not supported yet.");
+ break;
+ }
+
+ m_prober = prober;
+}
+
EffectSettings* MainWindow::effectSettingsByType(FFBEffectTypes type)
{
switch (type) {
else
name = dinfo.name;
- QString tag = QString("%1 [%2]").arg(name).arg(dinfo.path);
- ui->cbox_devices->addItem(tag, dinfo.path);
+ QString tag = QString("%1 [%2]").arg(name).arg(dinfo.id.toString());
+ ui->cbox_devices->addItem(tag, dinfo.id.toString());
}
}
ui->qstw_effectSpecifics->setCurrentWidget(effectSettingsByType(etype));
}
+void MainWindow::onInterfaceSelected(const int cboxIdx)
+{
+ Q_UNUSED(cboxIdx);
+ bool ok;
+ unsigned int rawIface;
+ DeviceProber::DeviceInterfaces iface;
+
+ rawIface = ui->cbox_interfaces->currentData().toUInt(&ok);
+ if (!ok) {
+ QMessageBox::critical(this, "Invalid data", "Invalid data passed as interface type.");
+ return;
+ }
+
+ iface = static_cast<DeviceProber::DeviceInterfaces>(rawIface);
+ createDeviceProber(iface);
+}
+
void MainWindow::onRefreshDevicesClicked()
{
- fillDeviceList();
+ //fillDeviceList();
}
void MainWindow::onRemoveEffectClicked()
Q_OBJECT
public:
- explicit MainWindow(std::shared_ptr<DeviceProber> prober, const QString& title, QWidget* parent = 0);
+ explicit MainWindow(const QString& title, QWidget* parent = 0);
~MainWindow();
private:
- enum class ErrorMessages { BAD_EFFECT_SLOT,
- CANT_REMOVE_EFFECT,
- CANT_START_EFFECT,
- CANT_UPLOAD_EFFECT };
+ enum class ErrorMessages {
+ BAD_EFFECT_SLOT,
+ CANT_REMOVE_EFFECT,
+ CANT_START_EFFECT,
+ CANT_UPLOAD_EFFECT
+ };
+ void createDeviceProber(const DeviceProber::DeviceInterfaces iface);
EffectSettings* effectSettingsByType(FFBEffectTypes type);
QString effectTypeToEffectName(const FFBEffectTypes type) const;
void fillDeviceList();
std::shared_ptr<FFBDevice> m_activeDevice;
ConditionEffectSettings* m_conditionEffSet;
ConstantEffectSettings* m_constantEffSet;
+ std::shared_ptr<DeviceProber> m_prober;
PeriodicEffectSettings* m_periodicEffSet;
RampEffectSettings* m_rampEffSet;
RumbleEffectSettings* m_rumbleEffSet;
- std::shared_ptr<DeviceProber> m_prober;
Ui::MainWindow* ui;
static const QString res_deviceErrorCap;
void onDeviceSelected(const int cboxIdx);
void onEffectSlotSelected(const int cboxIdx);
void onEffectTypeSelected(const int cboxIdx);
+ void onInterfaceSelected(const int cboxIdx);
void onRefreshDevicesClicked();
void onRemoveEffectClicked();
void onStartEffectClicked();
<x>0</x>
<y>0</y>
<width>444</width>
- <height>500</height>
+ <height>546</height>
</rect>
</property>
<property name="windowTitle">
<item row="1" column="1">
<widget class="QComboBox" name="cbox_effectSlots"/>
</item>
- <item row="2" column="0" colspan="2">
+ <item row="3" column="0" colspan="2">
<widget class="QPushButton" name="qpb_refreshDevices">
<property name="text">
<string>Refresh</string>
</property>
</widget>
</item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="ql_interface">
+ <property name="text">
+ <string>Interface:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QComboBox" name="cbox_interfaces"/>
+ </item>
</layout>
</item>
</layout>