From 113abdeb203d5118ad7d42de7da80ce10d031dce Mon Sep 17 00:00:00 2001 From: =?utf8?q?Michal=20Mal=C3=BD?= Date: Thu, 30 Jul 2015 00:44:49 +0200 Subject: [PATCH] First step towards modularizing for multiple FFB backends. --- CMakeLists.txt | 19 +- deviceprober.h | 31 +-- ffbdevice.cpp | 229 ++------------------- ffbdevice.h | 44 ++--- deviceprober.cpp => linuxdeviceprober.cpp | 30 +-- linuxdeviceprober.h | 27 +++ linuxffbdevice.cpp | 230 ++++++++++++++++++++++ linuxffbdevice.h | 39 ++++ main.cpp | 3 +- mainwindow.cpp | 56 +++++- mainwindow.h | 16 +- mainwindow.ui | 14 +- 12 files changed, 444 insertions(+), 294 deletions(-) rename deviceprober.cpp => linuxdeviceprober.cpp (70%) create mode 100644 linuxdeviceprober.h create mode 100644 linuxffbdevice.cpp create mode 100644 linuxffbdevice.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a452a5c..512ee97 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,12 @@ endif() 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) @@ -17,7 +23,6 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) set(FFBChecker_SRCS conditioneffectsettings.cpp constanteffectsettings.cpp - deviceprober.cpp effectsettings.cpp envelopesettings.cpp ffbconditioneffect.cpp @@ -37,14 +42,22 @@ set(FFBChecker_SRCS 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() diff --git a/deviceprober.h b/deviceprober.h index 778d28e..4d5c30d 100644 --- a/deviceprober.h +++ b/deviceprober.h @@ -3,33 +3,34 @@ #include "ffbdevice.h" #include -#include +#include #include -class DeviceProber : public QObject +class DeviceProber { - Q_OBJECT public: struct DeviceInfo { - QString path; + QVariant id; QString name; }; typedef QList DeviceList; - explicit DeviceProber(QObject* parent = 0); - DeviceList listDevices(); - std::shared_ptr openDevice(const QString& path); - -private: - std::list> 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 openDevice(const QString& id) = 0; -public slots: + const DeviceInterfaces type; +protected: + explicit DeviceProber() : type(DeviceInterfaces::NONE) {} }; + #endif // DEVICEPROBER_H + diff --git a/ffbdevice.cpp b/ffbdevice.cpp index 3b0efa8..a2424af 100644 --- a/ffbdevice.cpp +++ b/ffbdevice.cpp @@ -1,21 +1,5 @@ #include "ffbdevice.h" #include "ffbeffectfactory.h" -#include -#include - -#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& FFBDevice::availableConditionSubtypesList() const { @@ -34,7 +18,10 @@ const std::vector& FFBDevice::availableWaveformsList() const const std::shared_ptr 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(); @@ -42,6 +29,9 @@ const std::shared_ptr FFBDevice::effectParameters(const int 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; @@ -50,6 +40,12 @@ FFBEffect::FFBEffectStatus FFBDevice::effectStatusByIdx(const int idx) const 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(); } @@ -68,202 +64,3 @@ bool FFBDevice::hasPeriodicWaveform(PeriodicWaveforms id) const 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 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 parameters) -{ - struct ff_effect* kernelEff = nullptr; - std::shared_ptr 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; -} diff --git a/ffbdevice.h b/ffbdevice.h index 547bff4..45352e0 100644 --- a/ffbdevice.h +++ b/ffbdevice.h @@ -4,19 +4,9 @@ #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& path, const int maxEffectCount, QObject* parent = 0); +class FFBDevice { +public: const std::vector& availableConditionSubtypesList() const; const std::vector& availableEffectsList() const; const std::vector& availableWaveformsList() const; @@ -26,33 +16,25 @@ public: 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 parameters); - bool stopEffect(const int idx); - bool uploadEffect(const int idx, const FFBEffectTypes type, std::shared_ptr 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 parameters) = 0; + virtual bool stopEffect(const int idx) = 0; + virtual bool uploadEffect(const int idx, const FFBEffectTypes type, std::shared_ptr parameters) = 0; + +protected: + explicit FFBDevice(const int maxEffectCount) : + c_maxEffectCount(maxEffectCount) {} + std::vector m_availableConditionSubtypes; std::vector m_availableEffects; std::vector m_availablePeriodicWaveforms; std::vector> 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: }; diff --git a/deviceprober.cpp b/linuxdeviceprober.cpp similarity index 70% rename from deviceprober.cpp rename to linuxdeviceprober.cpp index b486833..6134cee 100644 --- a/deviceprober.cpp +++ b/linuxdeviceprober.cpp @@ -1,21 +1,23 @@ -#include "deviceprober.h" -#include "ffbdevice.h" +#include "linuxdeviceprober.h" #include +#include #include #include #include #include #include -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 dev : m_openedDevices) { + dev->close(); + } } -DeviceProber::DeviceList DeviceProber::listDevices() +DeviceProber::DeviceList LinuxDeviceProber::listDevices() { DeviceProber::DeviceList list; char deviceName[64]; @@ -35,7 +37,7 @@ DeviceProber::DeviceList DeviceProber::listDevices() 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); @@ -48,17 +50,17 @@ DeviceProber::DeviceList DeviceProber::listDevices() return list; } -std::shared_ptr DeviceProber::openDevice(const QString& path) +std::shared_ptr LinuxDeviceProber::openDevice(const QString& id) { /* Check if the device is already opened */ - for (std::shared_ptr dev : m_openedDevices) { - if (QString::compare(path, dev->path()) == 0) { - qDebug() << "Device" << path << "already opened"; + for (std::shared_ptr& 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; @@ -77,7 +79,7 @@ std::shared_ptr DeviceProber::openDevice(const QString& path) return nullptr; } - std::shared_ptr device(new FFBDevice(fd, path, maxEffectCount)); + std::shared_ptr device(new LinuxFFBDevice(fd, maxEffectCount, id)); if (!device->queryDeviceCapabilities()) { QMessageBox::critical(nullptr, res_ffbdeviceErrCap, "Unable to query device capabilities."); return nullptr; diff --git a/linuxdeviceprober.h b/linuxdeviceprober.h new file mode 100644 index 0000000..79ab94a --- /dev/null +++ b/linuxdeviceprober.h @@ -0,0 +1,27 @@ +#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 openDevice(const QString& id); + +private: + std::list> m_openedDevices; + + static const QString DEVICE_NODES_PATH; + static const QString res_ffbdeviceErrCap; + +signals: + +public slots: + +}; + +#endif // LINUXDEVICEPROBER_H diff --git a/linuxffbdevice.cpp b/linuxffbdevice.cpp new file mode 100644 index 0000000..c20804c --- /dev/null +++ b/linuxffbdevice.cpp @@ -0,0 +1,230 @@ +#include "linuxffbdevice.h" +#include "ffbeffectfactory.h" +#include +#include +#include +#include +#include +#include + +#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 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 parameters) +{ + struct ff_effect* kernelEff = nullptr; + std::shared_ptr 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; +} diff --git a/linuxffbdevice.h b/linuxffbdevice.h new file mode 100644 index 0000000..afb511a --- /dev/null +++ b/linuxffbdevice.h @@ -0,0 +1,39 @@ +#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 parameters); + bool stopEffect(const int idx); + bool uploadEffect(const int idx, const FFBEffectTypes type, std::shared_ptr 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 diff --git a/main.cpp b/main.cpp index e1a8b8b..7f72805 100644 --- a/main.cpp +++ b/main.cpp @@ -26,8 +26,7 @@ int main(int argc, char** argv) GlobalSettings::init(doSanityChecks); } - std::shared_ptr prober(new DeviceProber); - MainWindow* mWin = new MainWindow(prober, VERSION_STRING); + MainWindow* mWin = new MainWindow(VERSION_STRING); mWin->show(); return myApp.exec(); diff --git a/mainwindow.cpp b/mainwindow.cpp index 000c114..f8aa548 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,4 +1,5 @@ #include "globalsettings.h" +#include "linuxdeviceprober.h" #include "mainwindow.h" #include "ui_mainwindow.h" #include @@ -10,9 +11,8 @@ const QString MainWindow::res_effectPlaying("Playing"); const QString MainWindow::res_effectUploaded("Uploaded"); const QString MainWindow::res_inputFormatErrCap("Invalid input format."); -MainWindow::MainWindow(std::shared_ptr 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); @@ -32,10 +32,20 @@ MainWindow::MainWindow(std::shared_ptr prober, const QString& titl if (GlobalSettings::GS()->doSanityChecks) ui->ql_noChecksWarning->setHidden(true); + /* Fill the list of available interfaces */ + ui->cbox_interfaces->addItem("Linux API", static_cast::type>(DeviceProber::DeviceInterfaces::LINUX)); +#ifdef FFBC_HAVE_SDL2 + ui->cbox_interfaces->addItem("SDL2", static_cast::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())); @@ -43,6 +53,25 @@ MainWindow::MainWindow(std::shared_ptr prober, const QString& titl connect(ui->qpb_upload, SIGNAL(clicked()), this, SLOT(onUploadEffectClicked())); } +void MainWindow::createDeviceProber(const DeviceProber::DeviceInterfaces iface) +{ + std::shared_ptr prober; + + if (m_prober != nullptr) + m_prober->closeAllDevices(); + + switch (iface) { + case DeviceProber::DeviceInterfaces::LINUX: + prober = std::make_shared(); + 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) { @@ -90,8 +119,8 @@ void MainWindow::fillDeviceList() 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()); } } @@ -178,9 +207,26 @@ void MainWindow::onEffectTypeSelected(const int cboxIdx) 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(rawIface); + createDeviceProber(iface); +} + void MainWindow::onRefreshDevicesClicked() { - fillDeviceList(); + //fillDeviceList(); } void MainWindow::onRemoveEffectClicked() diff --git a/mainwindow.h b/mainwindow.h index 261912a..417d4b3 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -24,15 +24,18 @@ class MainWindow : public QMainWindow Q_OBJECT public: - explicit MainWindow(std::shared_ptr 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(); @@ -48,10 +51,10 @@ private: std::shared_ptr m_activeDevice; ConditionEffectSettings* m_conditionEffSet; ConstantEffectSettings* m_constantEffSet; + std::shared_ptr m_prober; PeriodicEffectSettings* m_periodicEffSet; RampEffectSettings* m_rampEffSet; RumbleEffectSettings* m_rumbleEffSet; - std::shared_ptr m_prober; Ui::MainWindow* ui; static const QString res_deviceErrorCap; @@ -64,6 +67,7 @@ private slots: 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(); diff --git a/mainwindow.ui b/mainwindow.ui index 59061df..da999b9 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -7,7 +7,7 @@ 0 0 444 - 500 + 546 @@ -39,13 +39,23 @@ - + Refresh + + + + Interface: + + + + + + -- 2.43.5