diff --git a/CMakeLists.txt b/CMakeLists.txt index d6b0eb3b1..37be4fa47 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -155,6 +155,7 @@ set(antimicrox_SOURCES src/gui/joybuttonwidget.cpp src/gui/joycontrolstickeditdialog.cpp src/gui/joydpadbuttonwidget.cpp + src/gui/joysensoreditdialog.cpp src/gui/joystickstatuswindow.cpp src/gui/joytabwidget.cpp src/gui/joytabwidgetcontainer.cpp @@ -169,6 +170,7 @@ set(antimicrox_SOURCES src/inputdaemon.cpp src/inputdevice.cpp src/inputdevicebitarraystatus.cpp + src/joyaccelerometersensor.cpp src/joyaxis.cpp src/joyaxiscontextmenu.cpp src/joybutton.cpp @@ -176,17 +178,28 @@ set(antimicrox_SOURCES src/joybuttonmousehelper.cpp src/joybuttonslot.cpp src/joybuttonstatusbox.cpp + src/joybuttontypes/joyaccelerometerbutton.cpp src/joybuttontypes/joyaxisbutton.cpp src/joybuttontypes/joycontrolstickbutton.cpp src/joybuttontypes/joycontrolstickmodifierbutton.cpp src/joybuttontypes/joydpadbutton.cpp src/joybuttontypes/joygradientbutton.cpp + src/joybuttontypes/joygyroscopebutton.cpp + src/joybuttontypes/joysensorbutton.cpp src/joycontrolstick.cpp src/joycontrolstickbuttonpushbutton.cpp src/joycontrolstickcontextmenu.cpp src/joycontrolstickpushbutton.cpp src/joycontrolstickstatusbox.cpp src/joydpad.cpp + src/joygyroscopesensor.cpp + src/joysensor.cpp + src/joysensorbuttonpushbutton.cpp + src/joysensorcontextmenu.cpp + src/joysensorfactory.cpp + src/joysensorpreset.cpp + src/joysensorpushbutton.cpp + src/joysensorstatusbox.cpp src/joystick.cpp src/keyboard/virtualkeyboardmousewidget.cpp src/keyboard/virtualkeypushbutton.cpp @@ -197,6 +210,7 @@ set(antimicrox_SOURCES src/mousedialog/mousebuttonsettingsdialog.cpp src/mousedialog/mousecontrolsticksettingsdialog.cpp src/mousedialog/mousedpadsettingsdialog.cpp + src/mousedialog/mousesensorsettingsdialog.cpp src/mousedialog/springmoderegionpreview.cpp src/mousedialog/uihelpers/mouseaxissettingsdialoghelper.cpp src/mousedialog/uihelpers/mousebuttonsettingsdialoghelper.cpp @@ -206,6 +220,7 @@ set(antimicrox_SOURCES src/pt1filter.cpp src/qtkeymapperbase.cpp src/sdleventreader.cpp + src/sensorpushbuttongroup.cpp src/setjoystick.cpp src/simplekeygrabberbutton.cpp src/statisticsestimator.cpp @@ -218,6 +233,7 @@ set(antimicrox_SOURCES src/uihelpers/joyaxiscontextmenuhelper.cpp src/uihelpers/joycontrolstickcontextmenuhelper.cpp src/uihelpers/joycontrolstickeditdialoghelper.cpp + src/uihelpers/joysensoriothreadhelper.cpp src/uihelpers/joytabwidgethelper.cpp src/vdpad.cpp src/xml/inputdevicexml.cpp @@ -269,6 +285,7 @@ set(antimicrox_HEADERS src/gui/joybuttonwidget.h src/gui/joycontrolstickeditdialog.h src/gui/joydpadbuttonwidget.h + src/gui/joysensoreditdialog.h src/gui/joystickstatuswindow.h src/gui/joytabwidget.h src/gui/joytabwidgetcontainer.h @@ -283,6 +300,7 @@ set(antimicrox_HEADERS src/inputdaemon.h src/inputdevice.h src/inputdevicebitarraystatus.h + src/joyaccelerometersensor.h src/joyaxis.h src/joyaxiscontextmenu.h src/joybutton.h @@ -290,17 +308,30 @@ set(antimicrox_HEADERS src/joybuttonmousehelper.h src/joybuttonslot.h src/joybuttonstatusbox.h + src/joybuttontypes/joyaccelerometerbutton.h src/joybuttontypes/joyaxisbutton.h src/joybuttontypes/joycontrolstickbutton.h src/joybuttontypes/joycontrolstickmodifierbutton.h src/joybuttontypes/joydpadbutton.h src/joybuttontypes/joygradientbutton.h + src/joybuttontypes/joygyroscopebutton.h + src/joybuttontypes/joysensorbutton.h src/joycontrolstick.h src/joycontrolstickbuttonpushbutton.h src/joycontrolstickcontextmenu.h src/joycontrolstickpushbutton.h src/joycontrolstickstatusbox.h src/joydpad.h + src/joygyroscopesensor.h + src/joysensor.h + src/joysensorbuttonpushbutton.h + src/joysensorcontextmenu.h + src/joysensordirection.h + src/joysensorfactory.h + src/joysensorpreset.h + src/joysensorpushbutton.h + src/joysensorstatusbox.h + src/joysensortype.h src/joystick.h src/keyboard/virtualkeyboardmousewidget.h src/keyboard/virtualkeypushbutton.h @@ -312,6 +343,7 @@ set(antimicrox_HEADERS src/mousedialog/mousecontrolsticksettingsdialog.h src/mousedialog/mousedpadsettingsdialog.h src/mousedialog/springmoderegionpreview.h + src/mousedialog/mousesensorsettingsdialog.h src/mousedialog/uihelpers/mouseaxissettingsdialoghelper.h src/mousedialog/uihelpers/mousebuttonsettingsdialoghelper.h src/mousedialog/uihelpers/mousecontrolsticksettingsdialoghelper.h @@ -320,6 +352,7 @@ set(antimicrox_HEADERS src/pt1filter.h src/qtkeymapperbase.h src/sdleventreader.h + src/sensorpushbuttongroup.h src/setjoystick.h src/simplekeygrabberbutton.h src/statisticsestimator.h @@ -332,6 +365,7 @@ set(antimicrox_HEADERS src/uihelpers/joyaxiscontextmenuhelper.h src/uihelpers/joycontrolstickcontextmenuhelper.h src/uihelpers/joycontrolstickeditdialoghelper.h + src/uihelpers/joysensoriothreadhelper.h src/uihelpers/joytabwidgethelper.h src/vdpad.h src/xml/inputdevicexml.h @@ -369,6 +403,7 @@ set(antimicrox_FORMS src/gui/extraprofilesettingsdialog.ui src/gui/gamecontrollermappingdialog.ui src/gui/joycontrolstickeditdialog.ui + src/gui/joysensoreditdialog.ui src/gui/joystickstatuswindow.ui src/gui/mainsettingsdialog.ui src/gui/mainwindow.ui @@ -804,4 +839,4 @@ if(BUILD_DOCS) else (DOXYGEN_FOUND) message("Doxygen need to be installed to generate the doxygen documentation") endif (DOXYGEN_FOUND) -endif(BUILD_DOCS) \ No newline at end of file +endif(BUILD_DOCS) diff --git a/src/gamecontroller/gamecontroller.cpp b/src/gamecontroller/gamecontroller.cpp index bf4fd2568..e25b7f8e2 100644 --- a/src/gamecontroller/gamecontroller.cpp +++ b/src/gamecontroller/gamecontroller.cpp @@ -172,6 +172,39 @@ int GameController::getNumberRawAxes() return SDL_CONTROLLER_AXIS_MAX; } +/** + * @brief Queries the data rate of the given sensor from SDL. + * @returns Data rate in events per second or zero if data rate is unavailable. + */ +double GameController::getRawSensorRate(JoySensorType type) +{ + double rate = 0; +#if SDL_VERSION_ATLEAST(2, 0, 16) + if (type == ACCELEROMETER) + rate = SDL_GameControllerGetSensorDataRate(controller, SDL_SENSOR_ACCEL); + else if (type == GYROSCOPE) + rate = SDL_GameControllerGetSensorDataRate(controller, SDL_SENSOR_GYRO); +#endif + if (qFuzzyIsNull(rate)) + WARN() << "Sensor rate is zero. Some calculations may be inaccurate!"; + return rate; +} + +/** + * @brief Queries if the hardware has the given sensor type. + * @returns True if the sensor is present, false otherwise. + */ +bool GameController::hasRawSensor(JoySensorType type) +{ +#if SDL_VERSION_ATLEAST(2, 0, 14) + if (type == ACCELEROMETER) + return SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL); + else if (type == GYROSCOPE) + return SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO); +#endif + return false; +} + int GameController::getNumberRawHats() { return 0; } void GameController::setCounterUniques(int counter) { counterUniques = counter; } diff --git a/src/gamecontroller/gamecontroller.h b/src/gamecontroller/gamecontroller.h index f9e576baf..a35940770 100644 --- a/src/gamecontroller/gamecontroller.h +++ b/src/gamecontroller/gamecontroller.h @@ -59,6 +59,8 @@ class GameController : public InputDevice virtual int getNumberRawButtons() override; virtual int getNumberRawAxes() override; virtual int getNumberRawHats() override; + virtual double getRawSensorRate(JoySensorType type) override; + virtual bool hasRawSensor(JoySensorType type) override; void setCounterUniques(int counter) override; QString getBindStringForAxis(int index, bool trueIndex = true); diff --git a/src/gamecontroller/gamecontrollerset.cpp b/src/gamecontroller/gamecontrollerset.cpp index 735ba2fe7..d382f8e3f 100644 --- a/src/gamecontroller/gamecontrollerset.cpp +++ b/src/gamecontroller/gamecontrollerset.cpp @@ -23,6 +23,7 @@ #include "gamecontrollertrigger.h" #include "inputdevice.h" #include "joycontrolstick.h" +#include "joysensor.h" #include "xml/joyaxisxml.h" #include "xml/joybuttonxml.h" #include "xml/joydpadxml.h" @@ -189,6 +190,9 @@ void GameControllerSet::readJoystickConfig(QXmlStreamReader *xml, QHashname() == "stick") && xml->isStartElement()) { getElemFromXml("stick", xml); + } else if ((xml->name() == "sensor") && xml->isStartElement()) + { + getElemFromXml("sensor", xml); } else if ((xml->name() == "vdpad") && xml->isStartElement()) { readConfDpad(xml, hatButtons, vdpadExists, dpadExists); @@ -228,6 +232,9 @@ void GameControllerSet::readConfig(QXmlStreamReader *xml) } else if ((xml->name() == "stick") && xml->isStartElement()) { getElemFromXml("stick", xml); + } else if ((xml->name() == "sensor") && xml->isStartElement()) + { + getElemFromXml("sensor", xml); } else if ((xml->name() == "dpad") && xml->isStartElement()) { getElemFromXml("dpad", xml); @@ -310,6 +317,11 @@ void GameControllerSet::getElemFromXml(QString elemName, QXmlStreamReader *xml) { xml->skipCurrentElement(); } + } else if (elemName == "sensor") + { + int type = xml->attributes().value("type").toString().toInt(); + JoySensor *sensor = getSensor(static_cast(type)); + readConf(sensor, xml); } } diff --git a/src/gamecontroller/gamecontrollerset.h b/src/gamecontroller/gamecontrollerset.h index 859431bb8..7416fa90b 100644 --- a/src/gamecontroller/gamecontrollerset.h +++ b/src/gamecontroller/gamecontrollerset.h @@ -26,6 +26,9 @@ class QXmlStreamReader; class InputDevice; +/** + * @brief A SetJoystick specialized for gamepads + */ class GameControllerSet : public SetJoystick { Q_OBJECT diff --git a/src/gamecontroller/xml/gamecontrollerxml.cpp b/src/gamecontroller/xml/gamecontrollerxml.cpp index 5ffed6e5d..e8f56ac14 100644 --- a/src/gamecontroller/xml/gamecontrollerxml.cpp +++ b/src/gamecontroller/xml/gamecontrollerxml.cpp @@ -25,7 +25,9 @@ #include "gamecontroller/gamecontrollerdpad.h" #include "gamecontroller/gamecontrollerset.h" #include "joybuttontypes/joycontrolstickbutton.h" +#include "joybuttontypes/joysensorbutton.h" #include "joycontrolstick.h" +#include "joysensor.h" #include @@ -39,6 +41,10 @@ GameControllerXml::GameControllerXml(GameController *gameController, QObject *pa m_gameController = gameController; } +/** + * @brief Reads the XML tree from a "joystick" tag + * @param xml XML stream at a "joystick" tag + */ void GameControllerXml::readJoystickConfig(QXmlStreamReader *xml) { int index = 0; @@ -141,6 +147,13 @@ void GameControllerXml::readJoystickConfig(QXmlStreamReader *xml) { m_gameController->setStickButtonName(index, buttonIndex, temp); } + } else if ((xml->name() == "sensorbuttonname") && xml->isStartElement()) + { + assignVariables(xml, index, buttonIndex, temp, false); + + if ((index >= 0) && !temp.isEmpty()) + m_gameController->setSensorButtonName(static_cast(index), + static_cast(buttonIndex), temp); } else if ((xml->name() == "dpadbuttonname") && xml->isStartElement()) { assignVariables(xml, index, buttonIndex, temp, false); @@ -234,6 +247,12 @@ void GameControllerXml::readJoystickConfig(QXmlStreamReader *xml) { m_gameController->setStickName(index, temp); } + } else if ((xml->name() == "sensorname") && xml->isStartElement()) + { + assignVariablesShort(xml, index, temp); + + if ((index >= 0) && !temp.isEmpty()) + m_gameController->setSensorName(static_cast(index), temp); } else if ((xml->name() == "dpadname") && xml->isStartElement()) { readJoystickConfigXmlLong(hatButtons, dpadNameExists, vdpadNameExists, xml); @@ -273,6 +292,10 @@ void GameControllerXml::readJoystickConfig(QXmlStreamReader *xml) } } +/** + * @brief Deserializes the given XML stream into a GameController object + * @param[in] xml The XML stream to read from + */ void GameControllerXml::readConfig(QXmlStreamReader *xml) { if (xml->isStartElement() && (xml->name() == m_gameController->getXmlName())) @@ -321,6 +344,9 @@ void GameControllerXml::readConfig(QXmlStreamReader *xml) } else if ((xml->name() == "controlstickbuttonname") && xml->isStartElement()) { readXmlNamesMiddle("controlstickbuttonname", xml); + } else if ((xml->name() == "sensorbuttonname") && xml->isStartElement()) + { + readXmlNamesMiddle("sensorbuttonname", xml); } else if ((xml->name() == "dpadbuttonname") && xml->isStartElement()) { readXmlNamesMiddle("dpadbuttonname", xml); @@ -330,6 +356,9 @@ void GameControllerXml::readConfig(QXmlStreamReader *xml) } else if ((xml->name() == "controlstickname") && xml->isStartElement()) { readXmlNamesShort("controlstickname", xml); + } else if ((xml->name() == "sensorname") && xml->isStartElement()) + { + readXmlNamesShort("sensorname", xml); } else if ((xml->name() == "dpadname") && xml->isStartElement()) { readXmlNamesShort("dpadname", xml); @@ -371,6 +400,10 @@ void GameControllerXml::readConfig(QXmlStreamReader *xml) } } +/** + * @brief Serializes a GameController object into the the given XML stream + * @param[in,out] xml The XML stream to write to + */ void GameControllerXml::writeConfig(QXmlStreamWriter *xml) { xml->writeStartElement(m_gameController->getXmlName()); @@ -396,6 +429,7 @@ void GameControllerXml::writeConfig(QXmlStreamWriter *xml) writeXmlForButtons(tempSet, xml); writeXmlForAxes(tempSet, xml); writeXmlForSticks(tempSet, xml); + writeXmlForSensors(tempSet, xml); writeXmlForVDpad(xml); xml->writeEndElement(); // @@ -512,6 +546,44 @@ void GameControllerXml::writeXmlForSticks(SetJoystick *tempSet, QXmlStreamWriter } } +/** + * @brief Writes all sensor button mappings in the current set to XML + * @param[in] tempSet To be deserialized SetJoystick + * @param[in,out] xml XML stream to which is written + */ +void GameControllerXml::writeXmlForSensors(SetJoystick *tempSet, QXmlStreamWriter *xml) +{ + auto sensors = tempSet->getSensors(); + for (const auto &sensor : sensors) + { + if (sensor != nullptr) + { + if (!sensor->getSensorName().isEmpty()) + { + xml->writeStartElement("sensorname"); + xml->writeAttribute("type", QString::number(sensor->getType())); + xml->writeCharacters(sensor->getSensorName()); + xml->writeEndElement(); + } + + auto buttons = sensor->getButtons(); + for (auto iter = buttons->cbegin(); iter != buttons->cend(); ++iter) + { + JoySensorButton *button = iter.value(); + + if ((button != nullptr) && !button->getButtonName().isEmpty()) + { + xml->writeStartElement("sensorbuttonname"); + xml->writeAttribute("type", QString::number(sensor->getType())); + xml->writeAttribute("button", QString::number(button->getRealJoyNumber())); + xml->writeCharacters(button->getButtonName()); + xml->writeEndElement(); + } + } + } + } +} + void GameControllerXml::writeXmlForVDpad(QXmlStreamWriter *xml) { QHashIterator currVDPad(m_gameController->getActiveSetJoystick()->getVdpads()); @@ -649,6 +721,13 @@ void GameControllerXml::readJoystickConfigXmlLong(QList #include @@ -34,7 +35,7 @@ const double Calibration::STICK_CAL_TAU = 0.045; const int Calibration::STICK_RATE_SAMPLES = 100; const int Calibration::CAL_TIMEOUT = 30; -Calibration::Calibration(InputDevice *joystick, QWidget *parent) +Calibration::Calibration(InputDevice *joystick, QDialog *parent) : QDialog(parent) , m_ui(new Ui::Calibration) , m_type(CAL_NONE) @@ -62,6 +63,18 @@ Calibration::Calibration(InputDevice *joystick, QWidget *parent) ++device_count; } + if (m_joystick->getActiveSetJoystick()->hasSensor(GYROSCOPE)) + { + m_ui->deviceComboBox->addItem(tr("Gyroscope"), QVariant(int(CAL_GYROSCOPE))); + ++device_count; + } + + if (m_joystick->getActiveSetJoystick()->hasSensor(ACCELEROMETER)) + { + m_ui->deviceComboBox->addItem(tr("Accelerometer"), QVariant(int(CAL_ACCELEROMETER))); + ++device_count; + } + connect(m_joystick, &InputDevice::destroyed, this, &Calibration::close); connect(m_ui->resetBtn, &QPushButton::clicked, this, &Calibration::resetSettings); connect(m_ui->saveBtn, &QPushButton::clicked, this, &Calibration::saveSettings); @@ -127,6 +140,34 @@ void Calibration::resetSettings() } } +/** + * @brief Shows the sensor offset calibration values to the user. + */ +void Calibration::showSensorCalibrationValues(bool offsetXvalid, double offsetX, bool offsetYvalid, double offsetY, + bool offsetZvalid, double offsetZ) +{ + QPalette paletteBlack = m_ui->offsetXValue->palette(); + paletteBlack.setColor(m_ui->offsetXValue->foregroundRole(), Qt::black); + QPalette paletteRed = m_ui->offsetXValue->palette(); + paletteRed.setColor(m_ui->offsetXValue->foregroundRole(), Qt::red); + + m_ui->offsetXValue->setPalette(offsetXvalid ? paletteBlack : paletteRed); + m_ui->offsetYValue->setPalette(offsetYvalid ? paletteBlack : paletteRed); + m_ui->offsetZValue->setPalette(offsetZvalid ? paletteBlack : paletteRed); + + if (m_type == CAL_ACCELEROMETER) + { + m_ui->offsetXValue->setText(QString::number(offsetX)); + m_ui->offsetYValue->setText(QString::number(offsetY)); + m_ui->offsetZValue->setText(QString::number(offsetZ)); + } else if (m_type == CAL_GYROSCOPE) + { + m_ui->offsetXValue->setText(QString::number(JoySensor::radToDeg(offsetX))); + m_ui->offsetYValue->setText(QString::number(JoySensor::radToDeg(offsetY))); + m_ui->offsetZValue->setText(QString::number(JoySensor::radToDeg(offsetZ))); + } +} + /** * @brief Shows the stick offset and gain calibration values to the user. */ @@ -226,6 +267,53 @@ void Calibration::selectTypeIndex(unsigned int type_index) m_ui->stickStatusBoxWidget->update(); connect(m_ui->startBtn, &QPushButton::clicked, this, &Calibration::startStickOffsetCalibration); + m_ui->startBtn->setEnabled(true); + m_ui->resetBtn->setEnabled(true); + } else if (m_type == CAL_ACCELEROMETER || m_type == CAL_GYROSCOPE) + { + if (m_type == CAL_ACCELEROMETER) + { + m_sensor = m_joystick->getActiveSetJoystick()->getSensor(ACCELEROMETER); + m_ui->steps->setText(tr("Accelerometer calibration sets the neutral controller orientation.")); + connect(m_ui->startBtn, &QPushButton::clicked, this, &Calibration::startAccelerometerCalibration); + } else if (m_type == CAL_GYROSCOPE) + { + m_sensor = m_joystick->getActiveSetJoystick()->getSensor(GYROSCOPE); + m_ui->steps->setText(tr("Gyroscope calibration corrects the sensor offset. " + "This prevents cursor movement while the controller is at rest.")); + connect(m_ui->startBtn, &QPushButton::clicked, this, &Calibration::startGyroscopeCalibration); + } + + m_ui->statusStack->setCurrentIndex(0); + m_calibrated = m_sensor->isCalibrated(); + + if (m_calibrated) + { + double offsetX, offsetY, offsetZ; + m_sensor->getCalibration(&offsetX, &offsetY, &offsetZ); + showSensorCalibrationValues(true, offsetX, true, offsetY, true, offsetZ); + } else + { + showSensorCalibrationValues(false, 0.0, false, 0.0, false, 0.0); + } + + m_ui->xAxisLabel->setVisible(true); + m_ui->yAxisLabel->setVisible(true); + m_ui->zAxisLabel->setVisible(true); + m_ui->offsetXLabel->setVisible(true); + m_ui->offsetYLabel->setVisible(true); + m_ui->offsetZLabel->setVisible(true); + m_ui->offsetXValue->setVisible(true); + m_ui->offsetYValue->setVisible(true); + m_ui->offsetZValue->setVisible(true); + + m_ui->resetBtn->setEnabled(m_calibrated); + m_ui->saveBtn->setEnabled(false); + + m_ui->sensorStatusBoxWidget->setFocus(); + m_ui->sensorStatusBoxWidget->setSensor(m_sensor); + m_ui->sensorStatusBoxWidget->update(); + m_ui->startBtn->setEnabled(true); m_ui->resetBtn->setEnabled(true); } @@ -265,13 +353,17 @@ void Calibration::resetCalibrationValues() if (m_type == CAL_STICK && m_stick != nullptr) { m_stick->resetCalibration(); - m_calibrated = false; - - m_ui->saveBtn->setEnabled(false); - m_ui->resetBtn->setEnabled(false); - m_ui->stickStatusBoxWidget->update(); showStickCalibrationValues(false, 0, false, 0, false, 0, false, 0); + } else if ((m_type == CAL_ACCELEROMETER || m_type == CAL_GYROSCOPE) && m_sensor != nullptr) + { + m_sensor->resetCalibration(); + showSensorCalibrationValues(false, 0, false, 0, false, 0); } + + m_calibrated = false; + m_ui->saveBtn->setEnabled(false); + m_ui->resetBtn->setEnabled(false); + m_ui->stickStatusBoxWidget->update(); update(); } @@ -323,6 +415,53 @@ void Calibration::deviceSelectionChanged(int index) } } +/** + * @brief Sensor data event handler. + * Performs gyroscope offset and accelerometer neutral orientation estimation + * and stops itself if the value was found or the process timed out. + * + * The gyroscope is only offset calibrated since gain calibration would require + * an accurate turntable to apply a known rotation rate on all axes. + * The offset is determined by calculating the mean output value at rest. + * + * The accelerometer calibration only rotates the coordinate system to the desired neutral + * orientation. Calibrating offset and gain would require an accurate fixture + * to align all axes parallel to gravity. + * The neutral angles are determined by calculating the mean output value at the desired position. + */ +void Calibration::onSensorOffsetData(float x, float y, float z) +{ + m_offset[0].process(x); + m_offset[1].process(y); + m_offset[2].process(z); + + bool xvalid = m_offset[0].calculateRelativeErrorSq() < CAL_ACCURACY_SQ && m_offset[0].getCount() > CAL_MIN_SAMPLES; + bool yvalid = m_offset[1].calculateRelativeErrorSq() < CAL_ACCURACY_SQ && m_offset[1].getCount() > CAL_MIN_SAMPLES; + bool zvalid = m_offset[2].calculateRelativeErrorSq() < CAL_ACCURACY_SQ && m_offset[2].getCount() > CAL_MIN_SAMPLES; + + showSensorCalibrationValues(xvalid, m_offset[0].getMean(), yvalid, m_offset[1].getMean(), zvalid, m_offset[2].getMean()); + + // Abort when end time is reached to avoid infinite loop + // in case of noisy sensors. + + if ((xvalid && yvalid && zvalid) || (QDateTime::currentDateTime() > m_end_time)) + { + m_changed = true; + disconnect(m_sensor, &JoySensor::moved, this, &Calibration::onSensorOffsetData); + disconnect(m_ui->startBtn, &QPushButton::clicked, this, nullptr); + if (m_type == CAL_ACCELEROMETER) + connect(m_ui->startBtn, &QPushButton::clicked, this, &Calibration::startAccelerometerCalibration); + else if (m_type == CAL_GYROSCOPE) + connect(m_ui->startBtn, &QPushButton::clicked, this, &Calibration::startGyroscopeCalibration); + m_ui->steps->setText(tr("Calibration completed.")); + m_ui->startBtn->setText(tr("Start calibration")); + m_ui->startBtn->setEnabled(true); + m_ui->saveBtn->setEnabled(true); + m_ui->deviceComboBox->setEnabled(true); + update(); + } +} + /** * @brief Stick data event handler. Performs stick offset estimation * and stops itself if the value was found or the process timed out. @@ -464,6 +603,12 @@ void Calibration::saveSettings() m_joystick->applyStickCalibration(m_index, offsetX, gainX, offsetY, gainY); showStickCalibrationValues(true, offsetX, true, gainX, true, offsetY, true, gainY); + } else if (m_type == CAL_ACCELEROMETER) + { + m_joystick->applyAccelerometerCalibration(m_offset[0].getMean(), m_offset[1].getMean(), m_offset[2].getMean()); + } else if (m_type == CAL_GYROSCOPE) + { + m_joystick->applyGyroscopeCalibration(m_offset[0].getMean(), m_offset[1].getMean(), m_offset[2].getMean()); } m_changed = false; m_calibrated = true; @@ -471,9 +616,104 @@ void Calibration::saveSettings() m_ui->resetBtn->setEnabled(true); } +/** + * @brief Shows user instructions for accelerometer calibration and initializes estimators. + */ +void Calibration::startAccelerometerCalibration() +{ + if (m_sensor == nullptr) + return; + + if (askConfirmation(tr("Calibration was saved for the preset. Do you really want to reset settings?"), !m_calibrated)) + { + m_offset[0].reset(); + m_offset[1].reset(); + m_offset[2].reset(); + + m_sensor->resetCalibration(); + m_calibrated = false; + + m_ui->steps->setText(tr("Hold the controller in the desired neutral position and press continue.")); + setWindowTitle(tr("Calibrating accelerometer")); + m_ui->startBtn->setText(tr("Continue calibration")); + update(); + + m_ui->resetBtn->setEnabled(false); + m_ui->deviceComboBox->setEnabled(false); + disconnect(m_ui->startBtn, &QPushButton::clicked, this, nullptr); + connect(m_ui->startBtn, &QPushButton::clicked, this, &Calibration::startAccelerometerAngleCalibration); + } +} +/** + * @brief Show calibration message to the user and enable accelerometer data event handler. + */ +void Calibration::startAccelerometerAngleCalibration() +{ + if (m_sensor != nullptr) + { + m_end_time = QDateTime::currentDateTime().addSecs(CAL_TIMEOUT); + m_ui->steps->setText( + tr("Collecting accelerometer data...\nPlease hold the controller still.\nThis can take up to %1 seconds.") + .arg(CAL_TIMEOUT)); + connect(m_sensor, &JoySensor::moved, this, &Calibration::onSensorOffsetData); + update(); + + m_ui->startBtn->setEnabled(false); + disconnect(m_ui->startBtn, &QPushButton::clicked, this, nullptr); + } +} + /** * @brief Shows user instructions for gyroscope calibration and initializes estimators. */ +void Calibration::startGyroscopeCalibration() +{ + if (m_sensor == nullptr) + return; + + if (askConfirmation(tr("Calibration was saved for the preset. Do you really want to reset settings?"), !m_calibrated)) + { + m_offset[0].reset(); + m_offset[1].reset(); + m_offset[2].reset(); + + m_sensor->resetCalibration(); + m_calibrated = false; + + m_ui->steps->setText(tr("Place the controller at rest, e.g. put it on the desk, " + "and press continue.")); + setWindowTitle(tr("Calibrating gyroscope")); + m_ui->startBtn->setText(tr("Continue calibration")); + update(); + + m_ui->resetBtn->setEnabled(false); + m_ui->deviceComboBox->setEnabled(false); + disconnect(m_ui->startBtn, &QPushButton::clicked, this, nullptr); + connect(m_ui->startBtn, &QPushButton::clicked, this, &Calibration::startGyroscopeOffsetCalibration); + } +} + +/** + * @brief Show calibration message to the user and enable gyroscope data event handler. + */ +void Calibration::startGyroscopeOffsetCalibration() +{ + if (m_sensor != nullptr) + { + m_end_time = QDateTime::currentDateTime().addSecs(CAL_TIMEOUT); + m_ui->steps->setText(tr("Collecting gyroscope data...\nThis can take up to %1 seconds.").arg(CAL_TIMEOUT)); + connect(m_sensor, &JoySensor::moved, this, &Calibration::onSensorOffsetData); + update(); + + m_ui->startBtn->setEnabled(false); + disconnect(m_ui->startBtn, &QPushButton::clicked, this, nullptr); + } +} + +/** + * @brief Shows user instructions for stick offset calibration, initializes estimators + * and connects stick data event handlers. + */ void Calibration::startStickOffsetCalibration() { if (m_stick == nullptr) diff --git a/src/gui/calibration.h b/src/gui/calibration.h index c7b527e8b..5041128ef 100644 --- a/src/gui/calibration.h +++ b/src/gui/calibration.h @@ -25,6 +25,7 @@ #include class JoyControlStick; +class JoySensor; class InputDevice; namespace Ui { @@ -40,18 +41,22 @@ class Calibration : public QDialog { CAL_NONE, CAL_STICK, + CAL_ACCELEROMETER, + CAL_GYROSCOPE, CAL_TYPE_MASK = 0x0000FFFF, CAL_INDEX_MASK = 0xFFFF0000, CAL_INDEX_POS = 16 }; - explicit Calibration(InputDevice *joystick, QWidget *parent = 0); + explicit Calibration(InputDevice *joystick, QDialog *parent = 0); ~Calibration(); protected: void resetCalibrationValues(); bool askConfirmation(QString message, bool confirmed); + void showSensorCalibrationValues(bool offsetXvalid, double offsetX, bool offsetYvalid, double offsetY, bool offsetZvalid, + double offsetZ); void showStickCalibrationValues(bool offsetXvalid, double offsetX, bool gainXvalid, double gainX, bool offsetYvalid, double offsetY, bool gainYvalid, double gainY); void hideCalibrationData(); @@ -65,9 +70,10 @@ class Calibration : public QDialog bool m_calibrated; bool m_changed; JoyControlStick *m_stick; + JoySensor *m_sensor; InputDevice *m_joystick; - StatisticsEstimator m_offset[2]; + StatisticsEstimator m_offset[3]; StatisticsEstimator m_min[2]; StatisticsEstimator m_max[2]; PT1Filter m_filter[2]; @@ -85,6 +91,10 @@ class Calibration : public QDialog public slots: void saveSettings(); + void startAccelerometerCalibration(); + void startAccelerometerAngleCalibration(); + void startGyroscopeCalibration(); + void startGyroscopeOffsetCalibration(); void startStickOffsetCalibration(); void startStickGainCalibration(); @@ -92,6 +102,7 @@ class Calibration : public QDialog void closeEvent(QCloseEvent *event) override; void resetSettings(); void deviceSelectionChanged(int index); + void onSensorOffsetData(float x, float y, float z); void onStickOffsetData(int x, int y); void onStickGainData(int x, int y); diff --git a/src/gui/calibration.ui b/src/gui/calibration.ui index 55cfd512d..2186bb606 100644 --- a/src/gui/calibration.ui +++ b/src/gui/calibration.ui @@ -59,6 +59,7 @@ 1 + @@ -285,6 +286,11 @@ + + JoySensorStatusBox + QWidget +
joysensorstatusbox.h
+
JoyControlStickStatusBox QWidget diff --git a/src/gui/joysensoreditdialog.cpp b/src/gui/joysensoreditdialog.cpp new file mode 100644 index 000000000..6a62d4563 --- /dev/null +++ b/src/gui/joysensoreditdialog.cpp @@ -0,0 +1,274 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "joysensoreditdialog.h" +#include "ui_joysensoreditdialog.h" + +#include "antkeymapper.h" +#include "buttoneditdialog.h" +#include "common.h" +#include "event.h" +#include "inputdevice.h" +#include "joybuttontypes/joysensorbutton.h" +#include "joysensor.h" +#include "mousedialog/mousesensorsettingsdialog.h" +#include "setjoystick.h" + +#include +#include +#include +#include +#include + +JoySensorEditDialog::JoySensorEditDialog(JoySensor *sensor, QWidget *parent) + : QDialog(parent, Qt::Window) + , m_ui(new Ui::JoySensorEditDialog) + , m_sensor(sensor) + , m_preset(sensor) +{ + m_ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose); + + auto min_width = m_ui->xCoordinateValue->fontMetrics().boundingRect(QString("X.XXXXXXXXXXX")).width(); + m_ui->xCoordinateValue->setMinimumWidth(min_width); + m_ui->xCoordinateValue->setAlignment(Qt::AlignLeft); + + int index = 0; + int current_preset_index = 0; + JoySensorPreset::Preset current_preset = m_preset.currentPreset(); + + QList presets = m_preset.getAvailablePresets(); + for (const auto &preset : presets) + { + m_ui->presetsComboBox->insertItem(index, m_preset.getPresetName(preset), preset); + if (preset == current_preset) + current_preset_index = index; + } + m_ui->presetsComboBox->setCurrentIndex(current_preset_index); + + PadderCommon::inputDaemonMutex.lock(); + + updateWindowTitleSensorName(); + if (m_sensor->getType() == ACCELEROMETER) + { + float value; + m_ui->accelerationValue->setText(QString::number(m_sensor->calculateDistance())); + value = JoySensor::radToDeg(m_sensor->calculatePitch()); + m_ui->pitchValue->setText(QString::number(value)); + value = JoySensor::radToDeg(m_sensor->calculateRoll()); + m_ui->rollValue->setText(QString::number(value)); + m_ui->maxZoneSlider->setMaximum(GlobalVariables::JoySensor::ACCEL_MAX); + m_ui->maxZoneSpinBox->setMaximum(GlobalVariables::JoySensor::ACCEL_MAX); + } else + { + m_ui->xCoordinateLabel->setText(tr("Roll (°/s)")); + m_ui->yCoordinateLabel->setText(tr("Pitch (°/s)")); + m_ui->zCoordinateLabel->setText(tr("Yaw (°/s)")); + m_ui->accelerationLabel->setVisible(false); + m_ui->accelerationValue->setVisible(false); + m_ui->pitchLabel->setVisible(false); + m_ui->pitchValue->setVisible(false); + m_ui->rollLabel->setVisible(false); + m_ui->rollValue->setVisible(false); + m_ui->maxZoneSlider->setMaximum(GlobalVariables::JoySensor::GYRO_MAX); + m_ui->maxZoneSpinBox->setMaximum(GlobalVariables::JoySensor::GYRO_MAX); + } + + m_ui->deadZoneSlider->setValue(m_sensor->getDeadZone()); + m_ui->deadZoneSpinBox->setValue(m_sensor->getDeadZone()); + + m_ui->maxZoneSlider->setValue(m_sensor->getMaxZone()); + m_ui->maxZoneSpinBox->setValue(m_sensor->getMaxZone()); + + m_ui->diagonalRangeSlider->setValue(m_sensor->getDiagonalRange()); + m_ui->diagonalRangeSpinBox->setValue(m_sensor->getDiagonalRange()); + + QString xCoorString = QString::number(m_sensor->getXCoordinate()); + m_ui->xCoordinateValue->setText(xCoorString); + + QString yCoorString = QString::number(m_sensor->getYCoordinate()); + m_ui->yCoordinateValue->setText(yCoorString); + + QString zCoorString = QString::number(m_sensor->getZCoordinate()); + m_ui->zCoordinateValue->setText(zCoorString); + + m_ui->sensorStatusBoxWidget->setSensor(m_sensor); + + m_ui->sensorNameLineEdit->setText(m_sensor->getSensorName()); + double validDistance = m_sensor->getDistanceFromDeadZone() * 100.0; + m_ui->fromSafeZoneValueLabel->setText(QString::number(validDistance)); + + int sensorDelay = m_sensor->getSensorDelay(); + m_ui->sensorDelaySlider->setValue(sensorDelay * .1); + m_ui->sensorDelayDoubleSpinBox->setValue(sensorDelay * .001); + + update(); + updateGeometry(); + + PadderCommon::inputDaemonMutex.unlock(); + + connect(m_ui->presetsComboBox, static_cast(&QComboBox::currentIndexChanged), this, + &JoySensorEditDialog::implementPresets); + + connect(m_ui->deadZoneSlider, &QSlider::valueChanged, m_ui->deadZoneSpinBox, &QDoubleSpinBox::setValue); + connect(m_ui->maxZoneSlider, &QSlider::valueChanged, m_ui->maxZoneSpinBox, &QDoubleSpinBox::setValue); + connect(m_ui->diagonalRangeSlider, &QSlider::valueChanged, m_ui->diagonalRangeSpinBox, &QSpinBox::setValue); + connect(m_ui->sensorDelaySlider, &QSlider::valueChanged, this, &JoySensorEditDialog::updateSensorDelaySpinBox); + + connect(m_ui->deadZoneSpinBox, static_cast(&QDoubleSpinBox::valueChanged), + m_ui->deadZoneSlider, &QSlider::setValue); + connect(m_ui->maxZoneSpinBox, static_cast(&QDoubleSpinBox::valueChanged), + m_ui->maxZoneSlider, &QSlider::setValue); + connect(m_ui->diagonalRangeSpinBox, static_cast(&QSpinBox::valueChanged), + m_ui->diagonalRangeSlider, &QSlider::setValue); + connect(m_ui->sensorDelayDoubleSpinBox, static_cast(&QDoubleSpinBox::valueChanged), + this, &JoySensorEditDialog::updateSensorDelaySlider); + + connect(m_ui->deadZoneSpinBox, static_cast(&QDoubleSpinBox::valueChanged), m_sensor, + &JoySensor::setDeadZone); + connect(m_ui->maxZoneSpinBox, static_cast(&QDoubleSpinBox::valueChanged), m_sensor, + &JoySensor::setMaxZone); + connect(m_ui->diagonalRangeSpinBox, static_cast(&QSpinBox::valueChanged), m_sensor, + &JoySensor::setDiagonalRange); + connect(m_ui->sensorDelayDoubleSpinBox, static_cast(&QDoubleSpinBox::valueChanged), + this, &JoySensorEditDialog::setSensorDelay); + + connect(m_sensor, &JoySensor::moved, this, &JoySensorEditDialog::updateSensorStats); + connect(m_ui->mouseSettingsPushButton, &QPushButton::clicked, this, &JoySensorEditDialog::openMouseSettingsDialog); + + connect(m_ui->sensorNameLineEdit, &QLineEdit::textEdited, m_sensor, &JoySensor::setSensorName); + connect(m_sensor, &JoySensor::sensorNameChanged, this, &JoySensorEditDialog::updateWindowTitleSensorName); +} + +JoySensorEditDialog::~JoySensorEditDialog() { delete m_ui; } + +/** + * @brief Preset combo box event handler. Applies selected preset to underlying sensor. + */ +void JoySensorEditDialog::implementPresets(int index) +{ + auto preset = static_cast(m_ui->presetsComboBox->itemData(index).toInt()); + m_preset.setSensorPreset(preset); + + m_ui->deadZoneSlider->setValue(m_sensor->getDeadZone()); + m_ui->deadZoneSpinBox->setValue(m_sensor->getDeadZone()); + + m_ui->maxZoneSlider->setValue(m_sensor->getMaxZone()); + m_ui->maxZoneSpinBox->setValue(m_sensor->getMaxZone()); + + m_ui->diagonalRangeSlider->setValue(m_sensor->getDiagonalRange()); + m_ui->diagonalRangeSpinBox->setValue(m_sensor->getDiagonalRange()); +} + +/** + * @brief Opens sensor mouse settings dialog + */ +void JoySensorEditDialog::openMouseSettingsDialog() +{ + m_ui->mouseSettingsPushButton->setEnabled(false); + + MouseSensorSettingsDialog *dialog = new MouseSensorSettingsDialog(m_sensor, this); + dialog->show(); + connect(this, SIGNAL(finished(int)), dialog, SLOT(close())); + connect(dialog, SIGNAL(finished(int)), this, SLOT(enableMouseSettingButton())); +} + +void JoySensorEditDialog::enableMouseSettingButton() { m_ui->mouseSettingsPushButton->setEnabled(true); } + +/** + * @brief Updates the numerical sensor values on the screen + */ +void JoySensorEditDialog::updateSensorStats(float x, float y, float z) +{ + float value; + + m_ui->xCoordinateValue->setText(QString::number(x)); + m_ui->yCoordinateValue->setText(QString::number(y)); + m_ui->zCoordinateValue->setText(QString::number(z)); + + if (m_sensor->getType() == ACCELEROMETER) + { + m_ui->accelerationValue->setText(QString::number(m_sensor->calculateDistance(x, y, z))); + value = JoySensor::radToDeg(m_sensor->calculatePitch(x, y, z)); + m_ui->pitchValue->setText(QString::number(value)); + value = JoySensor::radToDeg(m_sensor->calculateRoll(x, y, z)); + m_ui->rollValue->setText(QString::number(value)); + } + + double validDistance = m_sensor->getDistanceFromDeadZone(x, y, z) * 100.0; + m_ui->fromSafeZoneValueLabel->setText(QString::number(validDistance)); +} + +/** + * @brief Shows the sensor name in dialog title + */ +void JoySensorEditDialog::updateWindowTitleSensorName() +{ + QString temp = QString(tr("Set")).append(" "); + + if (!m_sensor->getSensorName().isEmpty()) + temp.append(m_sensor->getPartialName(false, true)); + else + temp.append(m_sensor->getPartialName()); + + if (m_sensor->getParentSet()->getIndex() != 0) + { + int setIndex = m_sensor->getParentSet()->getRealIndex(); + temp.append(" [").append(tr("Set %1").arg(setIndex)); + + QString setName = m_sensor->getParentSet()->getName(); + if (!setName.isEmpty()) + { + temp.append(": ").append(setName); + } + + temp.append("]"); + } + + setWindowTitle(temp); +} + +/** + * @brief Update QDoubleSpinBox value based on updated sensor delay value. + * @param Delay value obtained from JoySensor. + */ +void JoySensorEditDialog::updateSensorDelaySpinBox(int value) +{ + double temp = value / 1000.0; + m_ui->sensorDelayDoubleSpinBox->setValue(temp); +} + +/** + * @brief Update QSlider value based on value from QDoubleSpinBox. + * @param Value from QDoubleSpinBox. + */ +void JoySensorEditDialog::updateSensorDelaySlider(double value) +{ + int temp = value * 1000; + + if (m_ui->sensorDelaySlider->value() != temp) + m_ui->sensorDelaySlider->setValue(temp); +} + +/** + * @brief Sensor delay change event handler + * Converts the double value from the SpinBox to int and pass it to JoySensor. + */ +void JoySensorEditDialog::setSensorDelay(double value) +{ + QMetaObject::invokeMethod(m_sensor, "setSensorDelay", Q_ARG(unsigned int, value * 1000)); +} diff --git a/src/gui/joysensoreditdialog.h b/src/gui/joysensoreditdialog.h new file mode 100644 index 000000000..7618f72df --- /dev/null +++ b/src/gui/joysensoreditdialog.h @@ -0,0 +1,58 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include "joysensorpreset.h" + +#include + +class JoySensor; +class QWidget; + +namespace Ui { +class JoySensorEditDialog; +} + +/** + * @brief The configuration dialog for a JoySensor itself + */ +class JoySensorEditDialog : public QDialog +{ + Q_OBJECT + + public: + explicit JoySensorEditDialog(JoySensor *sensor, QWidget *parent = nullptr); + ~JoySensorEditDialog(); + + private: + Ui::JoySensorEditDialog *m_ui; + bool m_keypad_unlocked; + + JoySensor *m_sensor; + JoySensorPreset m_preset; + + private slots: + void implementPresets(int index); + + void openMouseSettingsDialog(); + void enableMouseSettingButton(); + void updateSensorStats(float x, float y, float z); + void updateWindowTitleSensorName(); + void updateSensorDelaySpinBox(int value); + void updateSensorDelaySlider(double value); + void setSensorDelay(double value); +}; diff --git a/src/gui/joysensoreditdialog.ui b/src/gui/joysensoreditdialog.ui new file mode 100644 index 000000000..55f657826 --- /dev/null +++ b/src/gui/joysensoreditdialog.ui @@ -0,0 +1,699 @@ + + + JoySensorEditDialog + + + + 0 + 0 + 702 + 570 + + + + + 0 + 0 + + + + + 0 + 0 + + + + Dialog + + + JoySensorButtonPushButton[isflashing="true"] { + background-color: rgb(0, 0, 255); + color: rgb(205, 197, 191); +} + + + + true + + + + + + 20 + + + + + + + + 0 + 0 + + + + + 200 + 200 + + + + + 16777215 + 16777215 + + + + + 0 + 0 + + + + + 200 + 200 + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + + + 0 + 0 + + + + 0 + + + + + + + + 0 + 0 + + + + X (m/s^2): + + + + + + + + 0 + 0 + + + + 0 + + + + + + + + 0 + 0 + + + + 0 + + + + + + + + 0 + 0 + + + + 0 + + + + + + + + 0 + 0 + + + + Acceleration (m/s^2): + + + + + + + + 0 + 0 + + + + 0 + + + + + + + + 0 + 0 + + + + Pitch (°): + + + + + + + + 0 + 0 + + + + Roll (°): + + + + + + + + 0 + 0 + + + + Z (m/s^2): + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + 0 + + + + + + + + 0 + 0 + + + + Y (m/s^2): + + + + + + + + 0 + 0 + + + + % Safe Zone: + + + + + + + + 0 + 0 + + + + 0 + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + + + + 10 + + + + + + + Presets: + + + + + + + + 282 + 0 + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 20 + + + + + + + + 6 + + + + + + + Dead Zone: + + + + + + + Dead zone value to use for an analog sensor. + + + 1 + + + 30 + + + 0 + + + 1 + + + 1 + + + Qt::Horizontal + + + + + + + Dead zone value to use for an analog sensor. + + + 2 + + + 0.000000000000000 + + + 30.000000000000000 + + + 0.500000000000000 + + + + + + + + + + + Max Zone: + + + + + + + Value when an analog sensor is considered moved 100%. + + + 0 + + + 30 + + + 0 + + + 10 + + + 1 + + + Qt::Horizontal + + + + + + + Value when an analog sensor is considered moved 100%. + + + 2 + + + 1.000000000000000 + + + 30.000000000000000 + + + 1.000000000000000 + + + + + + + + + + + Diagonal Range: + + + + + + + The area (in degrees) that each diagonal region occupies. + + + 1 + + + 90 + + + Qt::Horizontal + + + + + + + The area (in degrees) that each diagonal region occupies. + + + 1 + + + 90 + + + 90 + + + + + + + + + + + Sensor Delay: + + + + + + + Time lapsed before a direction change is taken into effect. + + + 0 + + + 100 + + + 1 + + + 10 + + + 0 + + + 0 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 0 + + + + + + + Time lapsed before a direction change is taken into effect. + + + false + + + s + + + 2 + + + 1.000000000000000 + + + 0.010000000000000 + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + 10 + + + 6 + + + + + Na&me: + + + sensorNameLineEdit + + + + + + + Specify the name of an analog sensor. + + + + + + + + + Mouse Settings + + + + .. + + + + + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + JoySensorStatusBox + QWidget +
joysensorstatusbox.h
+ 1 +
+
+ + + + buttonBox + accepted() + JoySensorEditDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + JoySensorEditDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/src/gui/joystickstatuswindow.cpp b/src/gui/joystickstatuswindow.cpp index 81fc1ac29..79387d9c8 100644 --- a/src/gui/joystickstatuswindow.cpp +++ b/src/gui/joystickstatuswindow.cpp @@ -25,6 +25,7 @@ #include "joybuttonstatusbox.h" #include "joybuttontypes/joydpadbutton.h" #include "joydpad.h" +#include "joysensor.h" #include #include @@ -96,6 +97,15 @@ JoystickStatusWindow::JoystickStatusWindow(InputDevice *joystick, QWidget *paren ui->joystickButtonsLabel->setText(QString::number(joystick->getNumberRawButtons())); ui->joystickHatsLabel->setText(QString::number(joystick->getNumberRawHats())); + if (joystick->hasRawSensor(ACCELEROMETER) && joystick->hasRawSensor(GYROSCOPE)) + ui->joystickSensorsLabel->setText(tr("Accelerometer + Gyroscope")); + else if (joystick->hasRawSensor(ACCELEROMETER)) + ui->joystickSensorsLabel->setText(tr("Accelerometer")); + else if (joystick->hasRawSensor(GYROSCOPE)) + ui->joystickSensorsLabel->setText(tr("Gyroscope")); + else + ui->joystickSensorsLabel->setText(tr("None")); + joystick->getActiveSetJoystick()->setIgnoreEventState(true); joystick->getActiveSetJoystick()->release(); joystick->resetButtonDownCount(); @@ -189,6 +199,69 @@ JoystickStatusWindow::JoystickStatusWindow(InputDevice *joystick, QWidget *paren ui->hatsGroupBox->setLayout(hatsBox); + QVBoxLayout *sensorsBox = new QVBoxLayout(); + sensorsBox->setSpacing(4); + + for (int i = 0; i < SENSOR_COUNT; ++i) + { + JoySensorType type = static_cast(i); + JoySensor *sensor = joystick->getActiveSetJoystick()->getSensor(type); + QProgressBar **axes; + if (type == ACCELEROMETER) + axes = m_accel_axes; + else + axes = m_gyro_axes; + + if (sensor != nullptr) + { + for (int i = 0; i < 3; ++i) + { + QHBoxLayout *hbox = new QHBoxLayout(); + + QLabel *axisLabel = new QLabel(); + axes[i] = new QProgressBar(); + if (type == ACCELEROMETER) + { + axes[i]->setMinimum(GlobalVariables::JoySensor::ACCEL_MIN * 1000); + axes[i]->setMaximum(GlobalVariables::JoySensor::ACCEL_MAX * 1000); + } else + { + axes[i]->setMinimum(GlobalVariables::JoySensor::GYRO_MIN * 1000); + axes[i]->setMaximum(GlobalVariables::JoySensor::GYRO_MAX * 1000); + } + axes[i]->setFormat("%v"); + if (i == 0) + { + axisLabel->setText(QString("%1 X").arg(sensor->sensorTypeName())); + axes[i]->setValue(sensor->getXCoordinate()); + } else if (i == 1) + { + axisLabel->setText(QString("%1 Y").arg(sensor->sensorTypeName())); + axes[i]->setValue(sensor->getYCoordinate()); + } else + { + axisLabel->setText(QString("%1 Z").arg(sensor->sensorTypeName())); + axes[i]->setValue(sensor->getZCoordinate()); + } + hbox->addWidget(axisLabel); + hbox->addWidget(axes[i]); + hbox->addSpacing(10); + sensorsBox->addLayout(hbox); + } + + if (type == ACCELEROMETER) + { + connect(sensor, &JoySensor::moved, this, &JoystickStatusWindow::updateAccelerometerValues); + } else + { + connect(sensor, &JoySensor::moved, this, &JoystickStatusWindow::updateGyroscopeValues); + } + } + } + + sensorsBox->addSpacerItem(new QSpacerItem(20, 20, QSizePolicy::Preferred, QSizePolicy::Fixed)); + ui->sensorsGroupBox->setLayout(sensorsBox); + // QString guidString = joystick->getGUIDString(); // if (!guidString.isEmpty()) // { @@ -246,4 +319,26 @@ void JoystickStatusWindow::restoreButtonStates(int code) void JoystickStatusWindow::obliterate() { this->done(QDialogButtonBox::DestructiveRole); } +/** + * @brief Accelerometer "moved" event handler + * Updates raw accelerometer values on the screen. + */ +void JoystickStatusWindow::updateAccelerometerValues(float valueX, float valueY, float valueZ) +{ + m_accel_axes[0]->setValue(valueX * 1000); + m_accel_axes[1]->setValue(valueY * 1000); + m_accel_axes[2]->setValue(valueZ * 1000); +} + +/** + * @brief Gyroscope "moved" event handler + * Updates raw gyroscope values on the screen. + */ +void JoystickStatusWindow::updateGyroscopeValues(float valueX, float valueY, float valueZ) +{ + m_gyro_axes[0]->setValue(JoySensor::radToDeg(valueX) * 1000); + m_gyro_axes[1]->setValue(JoySensor::radToDeg(valueY) * 1000); + m_gyro_axes[2]->setValue(JoySensor::radToDeg(valueZ) * 1000); +} + InputDevice *JoystickStatusWindow::getJoystick() const { return joystick; } diff --git a/src/gui/joystickstatuswindow.h b/src/gui/joystickstatuswindow.h index a29bca593..d3ee3ba20 100644 --- a/src/gui/joystickstatuswindow.h +++ b/src/gui/joystickstatuswindow.h @@ -22,12 +22,17 @@ #include class InputDevice; +class QProgressBar; class QWidget; namespace Ui { class JoystickStatusWindow; } +/** + * @brief The joystick "Properties" window. + * Shows various raw values to the user. + */ class JoystickStatusWindow : public QDialog { Q_OBJECT @@ -42,10 +47,14 @@ class JoystickStatusWindow : public QDialog Ui::JoystickStatusWindow *ui; InputDevice *joystick; + QProgressBar *m_accel_axes[3]; + QProgressBar *m_gyro_axes[3]; private slots: void restoreButtonStates(int code); void obliterate(); + void updateAccelerometerValues(float valueX, float valueY, float valueZ); + void updateGyroscopeValues(float valueX, float valueY, float valueZ); }; #endif // JOYSTICKSTATUSWINDOW_H diff --git a/src/gui/joystickstatuswindow.ui b/src/gui/joystickstatuswindow.ui index 741c5abae..eda1b87dc 100644 --- a/src/gui/joystickstatuswindow.ui +++ b/src/gui/joystickstatuswindow.ui @@ -185,6 +185,33 @@ JoyButtonStatusBox[isflashing="true"] { + + + + + + + 75 + true + + + + Sensors: + + + + + + + %1 + + + 10 + + + + + @@ -457,6 +484,13 @@ JoyButtonStatusBox[isflashing="true"] { + + + + Sensors + + + diff --git a/src/gui/joytabwidget.cpp b/src/gui/joytabwidget.cpp index 98f3aafec..855fbb00a 100644 --- a/src/gui/joytabwidget.cpp +++ b/src/gui/joytabwidget.cpp @@ -30,11 +30,14 @@ #include "joyaxiswidget.h" #include "joybuttontypes/joycontrolstickbutton.h" #include "joybuttontypes/joydpadbutton.h" +#include "joybuttontypes/joysensorbutton.h" #include "joybuttonwidget.h" #include "joycontrolstick.h" #include "joydpad.h" +#include "joysensor.h" #include "joystick.h" #include "quicksetdialog.h" +#include "sensorpushbuttongroup.h" #include "setnamesdialog.h" #include "stickpushbuttongroup.h" #include "vdpad.h" @@ -1766,6 +1769,18 @@ void JoyTabWidget::checkStickDisplay() } } +void JoyTabWidget::checkSensorDisplay() +{ + JoySensorButton *button = qobject_cast(sender()); + JoySensor *sensor = button->getSensor(); + if ((sensor != nullptr) && sensor->hasSlotsAssigned()) + { + SetJoystick *currentSet = m_joystick->getActiveSetJoystick(); + removeSetButtons(currentSet); + fillSetButtons(currentSet); + } +} + void JoyTabWidget::checkDPadButtonDisplay() { JoyDPadButton *button = qobject_cast(sender()); // static_cast @@ -1815,6 +1830,18 @@ void JoyTabWidget::checkStickEmptyDisplay() } } +void JoyTabWidget::checkSensorEmptyDisplay() +{ + SensorPushButtonGroup *group = qobject_cast(sender()); + JoySensor *sensor = group->getSensor(); + if ((sensor != nullptr) && !sensor->hasSlotsAssigned()) + { + SetJoystick *currentSet = m_joystick->getActiveSetJoystick(); + removeSetButtons(currentSet); + fillSetButtons(currentSet); + } +} + void JoyTabWidget::checkDPadButtonEmptyDisplay() { DPadPushButtonGroup *group = qobject_cast(sender()); // static_cast @@ -1979,6 +2006,75 @@ void JoyTabWidget::fillSetButtons(SetJoystick *set) column = 0; + QGridLayout *sensorGrid = nullptr; + QGroupBox *sensorGroup = nullptr; + int sensorGridColumn = 0; + int sensorGridRow = 0; + + for (size_t i = 0; i < SENSOR_COUNT; ++i) + { + JoySensorType type = static_cast(i); + if (!m_joystick->hasSensor(type)) + continue; + + JoySensor *sensor = currentSet->getSensor(type); + sensor->establishPropertyUpdatedConnection(); + QHash *sensorButtons = sensor->getButtons(); + + if (!hideEmptyButtons || sensor->hasSlotsAssigned()) + { + if (sensorGroup == nullptr) + sensorGroup = new QGroupBox(tr("Sensors"), this); + + if (sensorGrid == nullptr) + { + sensorGrid = new QGridLayout(); + sensorGridColumn = 0; + sensorGridRow = 0; + } + + QWidget *groupContainer = new QWidget(sensorGroup); + SensorPushButtonGroup *sensorButtonGroup = + new SensorPushButtonGroup(sensor, isKeypadUnlocked(), displayingNames, groupContainer); + if (hideEmptyButtons) + { + connect(sensorButtonGroup, &SensorPushButtonGroup::buttonSlotChanged, this, + &JoyTabWidget::checkSensorEmptyDisplay); + } + + connect(namesPushButton, &QPushButton::clicked, sensorButtonGroup, &SensorPushButtonGroup::toggleNameDisplay); + + if (sensorGridColumn > 1) + { + sensorGridColumn = 0; + sensorGridRow++; + } + + groupContainer->setLayout(sensorButtonGroup); + sensorGrid->addWidget(groupContainer, sensorGridRow, sensorGridColumn); + sensorGridColumn++; + } else + { + for (auto iter = sensorButtons->cbegin(); iter != sensorButtons->cend(); ++iter) + { + JoySensorButton *button = iter.value(); + button->establishPropertyUpdatedConnections(); + connect(button, &JoySensorButton::slotsChanged, this, &JoyTabWidget::checkSensorDisplay); + } + } + } + + if (sensorGroup != nullptr) + { + QSpacerItem *tempspacer = new QSpacerItem(10, 4, QSizePolicy::Minimum, QSizePolicy::Fixed); + QVBoxLayout *tempvbox = new QVBoxLayout; + tempvbox->addLayout(sensorGrid); + tempvbox->addItem(tempspacer); + sensorGroup->setLayout(tempvbox); + current_layout->addWidget(sensorGroup, row, column, 1, 2, Qt::AlignTop); + row++; + } + QGridLayout *hatGrid = nullptr; QGroupBox *hatGroup = nullptr; int hatGridColumn = 0; diff --git a/src/gui/joytabwidget.h b/src/gui/joytabwidget.h index d3dd3df50..28fbeb85d 100644 --- a/src/gui/joytabwidget.h +++ b/src/gui/joytabwidget.h @@ -130,11 +130,13 @@ class JoyTabWidget : public QWidget void checkForUnsavedProfile(int newindex = -1); void checkStickDisplay(); + void checkSensorDisplay(); void checkDPadButtonDisplay(); void checkAxisButtonDisplay(); void checkButtonDisplay(); void checkStickEmptyDisplay(); + void checkSensorEmptyDisplay(); void checkDPadButtonEmptyDisplay(); void checkAxisButtonEmptyDisplay(); void checkButtonEmptyDisplay(); diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp index 6ce965cca..3417cbc1e 100644 --- a/src/gui/mainwindow.cpp +++ b/src/gui/mainwindow.cpp @@ -36,6 +36,7 @@ #include "joycontrolstickbuttonpushbutton.h" #include "joycontrolstickpushbutton.h" #include "joydpadbuttonwidget.h" +#include "joysensorpushbutton.h" #include "joystick.h" #include "joystickstatuswindow.h" #include "joytabwidget.h" @@ -846,6 +847,13 @@ void MainWindow::enableFlashActions() stickWidget->tryFlash(); } + QList sensors = ui->tabWidget->widget(i)->findChildren(); + for (const auto &sensorWidget : sensors) + { + sensorWidget->enableFlashes(); + sensorWidget->tryFlash(); + } + QList list4 = ui->tabWidget->widget(i)->findChildren(); QListIterator iter4(list4); while (iter4.hasNext()) @@ -1093,19 +1101,11 @@ void MainWindow::openCalibration() if (device != nullptr) { - JoyControlStick *joystick = device->getActiveSetJoystick()->getJoyStick(0); - if (joystick != nullptr) - { - QPointer calibration = new Calibration(device); - calibration.data()->show(); + QPointer calibration = new Calibration(device); + calibration.data()->show(); - if (calibration.isNull()) - calibration.clear(); - } else - { - QMessageBox::information(this, tr("Calibration is not available."), - tr("Selected device doesn't have any joystick to calibrate.")); - } + if (calibration.isNull()) + calibration.clear(); } } } diff --git a/src/gui/mousesettingsdialog.cpp b/src/gui/mousesettingsdialog.cpp index 3b9313cbe..54c06ca59 100644 --- a/src/gui/mousesettingsdialog.cpp +++ b/src/gui/mousesettingsdialog.cpp @@ -34,6 +34,8 @@ MouseSettingsDialog::MouseSettingsDialog(QWidget *parent) , ui(new Ui::MouseSettingsDialog) { ui->setupUi(this); + ui->horizontalSpinBox->setMaximum(GlobalVariables::JoyButton::MAXMOUSESPEED); + ui->verticalSpinBox->setMaximum(GlobalVariables::JoyButton::MAXMOUSESPEED); setAttribute(Qt::WA_DeleteOnClose); JoyButtonMouseHelper *mouseHelper = JoyButton::getMouseHelper(); diff --git a/src/gui/mousesettingsdialog.ui b/src/gui/mousesettingsdialog.ui index 63b51f47e..236f018df 100644 --- a/src/gui/mousesettingsdialog.ui +++ b/src/gui/mousesettingsdialog.ui @@ -18,20 +18,21 @@ - - - - - - - Mouse Mode: - - - - - - - Cursor mode is used to move the mouse cursor + + + + + + + + Mouse Mode: + + + + + + + Cursor mode is used to move the mouse cursor around the screen relative to its current position depending on how much you move an axis or if a button is pressed. @@ -41,45 +42,45 @@ from the center of the screen depending on how much you move an axis. The mouse cursor will be returned to the center of the screen when the axis is moved back to the dead zone. - - - - - - - - Cursor + + + + + + + + Cursor + + + + + Spring + + + + + + + + + + + + + 160 + 0 + - - - Spring + Acceleration: - - - - - - - - - - - - 160 - 0 - - - - Acceleration: - - - - - - - Enhanced: Three tier curve that makes the mouse move + + + + + + Enhanced: Three tier curve that makes the mouse move slow on the low end of an axis and fast on the high end. Linear: Mouse moves proportionally to axis. @@ -97,57 +98,58 @@ Easing Quadratic: Axis high end is gradually accelerated over a period of time u Easing Cubic: Axis high end is gradually accelerated over a period of time using a Cubic curve. - - - - - - - - - Enhanced Precision - - - - - Linear - - - - - Quadratic - - - - - Cubic - - - - - Quadratic Extreme - - - - - Power Function - - - - - Easing Quadratic - - - - Easing Cubic - - - - - - - + + + + + + + + Enhanced Precision + + + + + Linear + + + + + Quadratic + + + + + Cubic + + + + + Quadratic Extreme + + + + + Power Function + + + + + Easing Quadratic + + + + + Easing Cubic + + + + + + + + @@ -166,7 +168,7 @@ a period of time using a Cubic curve. - + @@ -232,9 +234,6 @@ Speed: 1 - - 300 - 1 @@ -280,9 +279,6 @@ Speed: 1 - - 300 - 1 @@ -762,7 +758,7 @@ actual time that extra acceleration will be applied. 10 - + Spring Settings @@ -873,22 +869,22 @@ mouse position set by a non-relative spring. - - - - Qt::Vertical - - - - 20 - 40 - - - - + + + + Qt::Vertical + + + + 20 + 40 + + + + diff --git a/src/inputdaemon.cpp b/src/inputdaemon.cpp index 67b542236..3c84ef6d8 100644 --- a/src/inputdaemon.cpp +++ b/src/inputdaemon.cpp @@ -23,6 +23,7 @@ #include "globalvariables.h" #include "inputdevicebitarraystatus.h" #include "joydpad.h" +#include "joysensor.h" #include "joystick.h" #include "logger.h" #include "sdleventreader.h" @@ -191,6 +192,18 @@ void InputDaemon::refreshJoysticks() SDL_Joystick *sdlStick = SDL_GameControllerGetJoystick(controller); SDL_JoystickID tempJoystickID = SDL_JoystickInstanceID(sdlStick); + #if SDL_VERSION_ATLEAST(2, 0, 14) + if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO)) + { + SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE); + } + + if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL)) + { + SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE); + } + #endif + // Check if device has already been grabbed. if (!m_joysticks->contains(tempJoystickID)) { @@ -698,6 +711,10 @@ InputDaemon::createOrGrabBitStatusEntry(QHash *sdlEventQueue) { SDL_Event event; @@ -805,6 +822,37 @@ void InputDaemon::firstInputPass(QQueue *sdlEventQueue) break; } +#if SDL_VERSION_ATLEAST(2, 0, 14) + case SDL_CONTROLLERSENSORUPDATE: { + InputDevice *joy = trackcontrollers.value(event.caxis.which); + + if (joy != nullptr) + { + SetJoystick *set = joy->getActiveSetJoystick(); + JoySensorType sensor_type; + if (event.csensor.sensor == SDL_SENSOR_ACCEL) + sensor_type = ACCELEROMETER; + else if (event.csensor.sensor == SDL_SENSOR_GYRO) + sensor_type = GYROSCOPE; + JoySensor *sensor = set->getSensor(sensor_type); + + if (sensor != nullptr) + { + InputDeviceBitArrayStatus *temp = createOrGrabBitStatusEntry(&releaseEventsGenerated, joy, false); + temp->changeSensorStatus(sensor_type, event.csensor.sensor == 0); + + InputDeviceBitArrayStatus *pending = createOrGrabBitStatusEntry(&pendingEventValues, joy); + pending->changeSensorStatus(sensor_type, !sensor->inDeadZone(event.csensor.data)); + sdlEventQueue->append(event); + } + } else + { + sdlEventQueue->append(event); + } + break; + } +#endif + case SDL_CONTROLLERBUTTONDOWN: case SDL_CONTROLLERBUTTONUP: { InputDevice *joy = trackcontrollers.value(event.cbutton.which); @@ -842,6 +890,9 @@ void InputDaemon::firstInputPass(QQueue *sdlEventQueue) } } +/** + * @brief Postprocesses fetched raw events. + */ void InputDaemon::modifyUnplugEvents(QQueue *sdlEventQueue) { QHashIterator genIter(getReleaseEventsGeneratedLocal()); @@ -940,6 +991,12 @@ void InputDaemon::modifyUnplugEvents(QQueue *sdlEventQueue) break; } +#if SDL_VERSION_ATLEAST(2, 0, 14) + case SDL_CONTROLLERSENSORUPDATE: { + tempQueue.enqueue(event); + break; + } +#endif case SDL_CONTROLLERBUTTONDOWN: case SDL_CONTROLLERBUTTONUP: { tempQueue.enqueue(event); @@ -981,6 +1038,10 @@ QBitArray InputDaemon::createUnplugEventBitArray(InputDevice *device) return unplugBitArray; } +/** + * @brief Dispatches postprocessed SDL events to the input objects like + * JoyAxis or JoyButton and activates them at the end. + */ void InputDaemon::secondInputPass(QQueue *sdlEventQueue) { QMap uniques = QMap(); @@ -1090,6 +1151,34 @@ void InputDaemon::secondInputPass(QQueue *sdlEventQueue) break; } +#if SDL_VERSION_ATLEAST(2, 0, 14) + case SDL_CONTROLLERSENSORUPDATE: { + InputDevice *joy = trackcontrollers.value(event.csensor.which); + + if (joy != nullptr) + { + SetJoystick *set = joy->getActiveSetJoystick(); + JoySensor *sensor; + if (event.csensor.sensor == SDL_SENSOR_ACCEL) + sensor = set->getSensor(ACCELEROMETER); + else if (event.csensor.sensor == SDL_SENSOR_GYRO) + sensor = set->getSensor(GYROSCOPE); + else + Q_ASSERT(false); + + if (sensor != nullptr) + { + sensor->queuePendingEvent(event.csensor.data); + + if (!activeDevices.contains(event.csensor.which)) + activeDevices.insert(event.csensor.which, joy); + } + } + + break; + } +#endif + case SDL_CONTROLLERBUTTONDOWN: case SDL_CONTROLLERBUTTONUP: { InputDevice *joy = trackcontrollers.value(event.cbutton.which); @@ -1150,6 +1239,7 @@ void InputDaemon::secondInputPass(QQueue *sdlEventQueue) InputDevice *tempDevice = activeDevIter.next().value(); tempDevice->activatePossibleControlStickEvents(); tempDevice->activatePossibleAxisEvents(); + tempDevice->activatePossibleSensorEvents(); tempDevice->activatePossibleDPadEvents(); tempDevice->activatePossibleVDPadEvents(); tempDevice->activatePossibleButtonEvents(); diff --git a/src/inputdaemon.h b/src/inputdaemon.h index 9871583d9..5f28b15a2 100644 --- a/src/inputdaemon.h +++ b/src/inputdaemon.h @@ -31,6 +31,11 @@ class GameController; class SDLEventReader; class QThread; +/** + * @brief Fetches hardware events from SDL and dispatch them to + * input objects like JoyAxis or JoyButton. + * Runs in a separate thread. + */ class InputDaemon : public QObject { Q_OBJECT diff --git a/src/inputdevice.cpp b/src/inputdevice.cpp index a44bdd175..416dff08d 100644 --- a/src/inputdevice.cpp +++ b/src/inputdevice.cpp @@ -23,8 +23,10 @@ #include "globalvariables.h" #include "joybuttontypes/joycontrolstickbutton.h" #include "joybuttontypes/joydpadbutton.h" +#include "joybuttontypes/joysensorbutton.h" #include "joycontrolstick.h" #include "joydpad.h" +#include "joysensor.h" #include "vdpad.h" #include @@ -567,6 +569,12 @@ int InputDevice::getNumberHats() { return getActiveSetJoystick()->getNumberHats( int InputDevice::getNumberSticks() { return getActiveSetJoystick()->getNumberSticks(); } +/** + * @brief Checks if this input device has a sensor of given type + * @returns True if sensor is present, false otherwise + */ +bool InputDevice::hasSensor(JoySensorType type) { return getActiveSetJoystick()->hasSensor(type); } + int InputDevice::getNumberVDPads() { return getActiveSetJoystick()->getNumberVDPads(); } SetJoystick *InputDevice::getSetJoystick(int index) { return getJoystick_sets().value(index); } @@ -610,6 +618,20 @@ void InputDevice::changeSetStickButtonAssociation(int button_index, int stick_in button->setChangeSetCondition(tempmode, true); } +/** + * @brief Creates reverse set change button mapping for toggle and while-hold set + * change mappings. + */ +void InputDevice::changeSetSensorButtonAssociation(JoySensorDirection direction, JoySensorType type, int originset, + int newset, int mode) +{ + JoySensorButton *button = getJoystick_sets().value(newset)->getSensor(type)->getDirectionButton(direction); + + JoyButton::SetChangeCondition tempmode = static_cast(mode); + button->setChangeSetSelection(originset); + button->setChangeSetCondition(tempmode, true); +} + void InputDevice::changeSetDPadButtonAssociation(int button_index, int dpad_index, int originset, int newset, int mode) { JoyDPadButton *button = getJoystick_sets().value(newset)->getJoyDPad(dpad_index)->getJoyButton(button_index); @@ -756,6 +778,20 @@ void InputDevice::stickButtonUpEvent(int setindex, int stickindex, int buttonind buttonUpEvent(setindex, buttonindex); } +void InputDevice::sensorButtonDownEvent(int setindex, JoySensorType type, JoySensorDirection direction) +{ + Q_UNUSED(type); + + buttonDownEvent(setindex, direction); +} + +void InputDevice::sensorButtonUpEvent(int setindex, JoySensorType type, JoySensorDirection direction) +{ + Q_UNUSED(type); + + buttonUpEvent(setindex, direction); +} + void InputDevice::setButtonName(int index, QString tempName) { QHashIterator iter(getJoystick_sets()); @@ -822,6 +858,32 @@ void InputDevice::setStickButtonName(int stickIndex, int buttonIndex, QString te } } +/** + * @brief Sets the name of a mapped sensor button in all sets + * Used during XML loading. + * @param type The sensor type which has the to be renamed button + * @param direction The direction of the to be renamed button + * @param tempName The new name + */ +void InputDevice::setSensorButtonName(JoySensorType type, JoySensorDirection direction, QString tempName) +{ + auto sets = getJoystick_sets(); + for (auto &tempSet : sets) + { + disconnect(tempSet, &SetJoystick::setStickButtonNameChange, this, &InputDevice::updateSetStickButtonNames); + JoySensor *sensor = tempSet->getSensor(type); + + if (sensor != nullptr) + { + JoySensorButton *button = sensor->getDirectionButton(direction); + + if (button != nullptr) + button->setButtonName(tempName); + } + + connect(tempSet, &SetJoystick::setSensorButtonNameChange, this, &InputDevice::updateSetSensorButtonNames); + } +} void InputDevice::setDPadButtonName(int dpadIndex, int buttonIndex, QString tempName) { QHashIterator iter(getJoystick_sets()); @@ -900,6 +962,27 @@ void InputDevice::setStickName(int stickIndex, QString tempName) } } +/** + * @brief Sets the name of a sensor in all sets + * Used during XML loading. + * @param type The sensor type to be renamed + * @param tempName The new name + */ +void InputDevice::setSensorName(JoySensorType type, QString tempName) +{ + auto sets = getJoystick_sets(); + for (auto &tempSet : sets) + { + disconnect(tempSet, &SetJoystick::setSensorNameChange, this, &InputDevice::updateSetSensorNames); + JoySensor *sensor = tempSet->getSensor(type); + + if (sensor != nullptr) + sensor->setSensorName(tempName); + + connect(tempSet, &SetJoystick::setSensorNameChange, this, &InputDevice::updateSetSensorNames); + } +} + void InputDevice::setDPadName(int dpadIndex, QString tempName) { QHashIterator iter(getJoystick_sets()); @@ -973,6 +1056,24 @@ void InputDevice::updateSetStickButtonNames(int stickIndex, int buttonIndex) } } +/** + * @brief Rename mapped sensor button in all sets to the name in the active set + * @param type The sensor type which has the to be renamed button + * @param direction The direction of the to be renamed button + */ +void InputDevice::updateSetSensorButtonNames(JoySensorType type, JoySensorDirection direction) +{ + JoySensor *sensor = getActiveSetJoystick()->getSensor(type); + + if (sensor != nullptr) + { + JoySensorButton *button = sensor->getDirectionButton(direction); + + if (button != nullptr) + setSensorButtonName(type, direction, button->getButtonName()); + } +} + void InputDevice::updateSetDPadButtonNames(int dpadIndex, int buttonIndex) { JoyDPad *dpad = getActiveSetJoystick()->getJoyDPad(dpadIndex); @@ -1015,6 +1116,18 @@ void InputDevice::updateSetStickNames(int stickIndex) setStickName(stickIndex, stick->getStickName()); } +/** + * @brief Rename sensor in all sets to the name in the current set + * @param type The sensor to rename + */ +void InputDevice::updateSetSensorNames(JoySensorType type) +{ + JoySensor *sensor = getActiveSetJoystick()->getSensor(type); + + if (sensor != nullptr) + setSensorName(type, sensor->getSensorName()); +} + void InputDevice::updateSetDPadNames(int dpadIndex) { JoyDPad *dpad = getActiveSetJoystick()->getJoyDPad(dpadIndex); @@ -1037,6 +1150,9 @@ void InputDevice::resetButtonDownCount() emit released(joyNumber); } +/** + * @brief Establishes necessary connections for set change slots + */ void InputDevice::enableSetConnections(SetJoystick *setstick) { connect(setstick, &SetJoystick::setChangeActivated, this, &InputDevice::resetButtonDownCount); @@ -1048,6 +1164,7 @@ void InputDevice::enableSetConnections(SetJoystick *setstick) connect(setstick, &SetJoystick::setAssignmentDPadChanged, this, &InputDevice::changeSetDPadButtonAssociation); connect(setstick, &SetJoystick::setAssignmentVDPadChanged, this, &InputDevice::changeSetVDPadButtonAssociation); connect(setstick, &SetJoystick::setAssignmentStickChanged, this, &InputDevice::changeSetStickButtonAssociation); + connect(setstick, &SetJoystick::setAssignmentSensorChanged, this, &InputDevice::changeSetSensorButtonAssociation); connect(setstick, &SetJoystick::setAssignmentAxisThrottleChanged, this, &InputDevice::propogateSetAxisThrottleChange); connect(setstick, &SetJoystick::setButtonClick, this, &InputDevice::buttonDownEvent); @@ -1064,15 +1181,19 @@ void InputDevice::enableSetConnections(SetJoystick *setstick) connect(setstick, &SetJoystick::setStickButtonClick, this, &InputDevice::stickButtonDownEvent); connect(setstick, &SetJoystick::setStickButtonRelease, this, &InputDevice::stickButtonUpEvent); + connect(setstick, &SetJoystick::setSensorButtonClick, this, &InputDevice::sensorButtonDownEvent); + connect(setstick, &SetJoystick::setSensorButtonRelease, this, &InputDevice::sensorButtonUpEvent); connect(setstick, &SetJoystick::setButtonNameChange, this, &InputDevice::updateSetButtonNames); connect(setstick, &SetJoystick::setAxisButtonNameChange, this, &InputDevice::updateSetAxisButtonNames); connect(setstick, &SetJoystick::setStickButtonNameChange, this, &InputDevice::updateSetStickButtonNames); + connect(setstick, &SetJoystick::setSensorButtonNameChange, this, &InputDevice::updateSetSensorButtonNames); connect(setstick, &SetJoystick::setDPadButtonNameChange, this, &InputDevice::updateSetDPadButtonNames); connect(setstick, &SetJoystick::setVDPadButtonNameChange, this, &InputDevice::updateSetVDPadButtonNames); connect(setstick, &SetJoystick::setAxisNameChange, this, &InputDevice::updateSetAxisNames); connect(setstick, &SetJoystick::setStickNameChange, this, &InputDevice::updateSetStickNames); + connect(setstick, &SetJoystick::setSensorNameChange, this, &InputDevice::updateSetSensorNames); connect(setstick, &SetJoystick::setDPadNameChange, this, &InputDevice::updateSetDPadNames); connect(setstick, &SetJoystick::setVDPadNameChange, this, &InputDevice::updateSetVDPadNames); } @@ -1224,7 +1345,9 @@ QString InputDevice::getDescription() full_desc = full_desc + " " + QObject::tr("Game Controller: %1").arg(gameControllerStatus) + "\n " + QObject::tr("# of Axes: %1").arg(getNumberRawAxes()) + "\n " + QObject::tr("# of Buttons: %1").arg(getNumberRawButtons()) + "\n " + - QObject::tr("# of Hats: %1").arg(getNumberHats()) + "\n"; + QObject::tr("# of Hats: %1").arg(getNumberHats()) + "\n " + + QObject::tr("Accelerometer: %1").arg(hasSensor(ACCELEROMETER)) + "\n " + + QObject::tr("Gyroscope: %1").arg(hasSensor(GYROSCOPE)) + "\n"; return full_desc; } @@ -1353,6 +1476,20 @@ void InputDevice::activatePossibleAxisEvents() } } +void InputDevice::activatePossibleSensorEvents() +{ + SetJoystick *currentSet = getActiveSetJoystick(); + JoySensor *sensor = nullptr; + + for (size_t i = 0; i < SENSOR_COUNT; ++i) + { + JoySensorType type = static_cast(i); + sensor = currentSet->getSensor(type); + if ((sensor != nullptr) && sensor->hasPendingEvent()) + sensor->activatePendingEvent(); + } +} + void InputDevice::activatePossibleDPadEvents() { SetJoystick *currentSet = getActiveSetJoystick(); @@ -1623,11 +1760,44 @@ SDL_Joystick *InputDevice::getJoyHandle() const { return m_joyhandle; } */ void InputDevice::applyStickCalibration(int index, double offsetX, double gainX, double offsetY, double gainY) { - for (auto iter = joystick_sets.begin(); iter != joystick_sets.end(); ++iter) + for (auto &set : joystick_sets) { - SetJoystick *set = iter.value(); JoyControlStick *stick = set->getSticks().value(index); if (stick != nullptr) stick->setCalibration(offsetX, gainX, offsetY, gainY); } } + +/** + * @brief Applies calibration to the specified accelerometer in all sets + * See JoySensor::setCalibration + * @param[in] offsetX Offset angle around the X axis + * @param[in] offsetY Offset angle around the Y axis + * @param[in] offsetZ Offset angle around the Z axis + */ +void InputDevice::applyAccelerometerCalibration(double offsetX, double offsetY, double offsetZ) +{ + for (auto &set : joystick_sets) + { + JoySensor *accelerometer = set->getSensor(ACCELEROMETER); + if (accelerometer != nullptr) + accelerometer->setCalibration(offsetX, offsetY, offsetZ); + } +} + +/** + * @brief Applies calibration to the specified gyroscope in all sets + * See JoySensor::setCalibration + * @param[in] offsetX Offset value for X axis + * @param[in] offsetY Offset value for Y axis + * @param[in] offsetZ Offset value for Z axis + */ +void InputDevice::applyGyroscopeCalibration(double offsetX, double offsetY, double offsetZ) +{ + for (auto &set : joystick_sets) + { + JoySensor *gyroscope = set->getSensor(GYROSCOPE); + if (gyroscope != nullptr) + gyroscope->setCalibration(offsetX, offsetY, offsetZ); + } +} diff --git a/src/inputdevice.h b/src/inputdevice.h index 277bf5ac5..effcc6fc2 100644 --- a/src/inputdevice.h +++ b/src/inputdevice.h @@ -19,6 +19,8 @@ #ifndef INPUTDEVICE_H #define INPUTDEVICE_H +#include "joysensordirection.h" +#include "joysensortype.h" #include "setjoystick.h" #include @@ -29,6 +31,9 @@ class QXmlStreamReader; class QXmlStreamWriter; class QSettings; +/** + * @brief Represents a hardware input device, e.g a joystick or controller. + */ class InputDevice : public QObject { Q_OBJECT @@ -41,6 +46,7 @@ class InputDevice : public QObject virtual int getNumberAxes(); virtual int getNumberHats(); virtual int getNumberSticks(); + virtual bool hasSensor(JoySensorType type); virtual int getNumberVDPads(); int getJoyNumber(); @@ -80,17 +86,21 @@ class InputDevice : public QObject void setButtonName(int index, QString tempName); // InputDeviceXml class void setAxisButtonName(int axisIndex, int buttonIndex, QString tempName); // InputDeviceXml class void setStickButtonName(int stickIndex, int buttonIndex, QString tempName); // InputDeviceXml class + void setSensorButtonName(JoySensorType type, JoySensorDirection direction, QString tempName); void setDPadButtonName(int dpadIndex, int buttonIndex, QString tempName); // InputDeviceXml class void setVDPadButtonName(int vdpadIndex, int buttonIndex, QString tempName); // InputDeviceXml class void setAxisName(int axisIndex, QString tempName); // InputDeviceAxis class void setStickName(int stickIndex, QString tempName); // InputDeviceStick class + void setSensorName(JoySensorType type, QString tempName); void setDPadName(int dpadIndex, QString tempName); // InputDeviceHat class void setVDPadName(int vdpadIndex, QString tempName); // InputDeviceVDPad class virtual int getNumberRawButtons() = 0; virtual int getNumberRawAxes() = 0; virtual int getNumberRawHats() = 0; + virtual double getRawSensorRate(JoySensorType type) = 0; + virtual bool hasRawSensor(JoySensorType type) = 0; int getDeviceKeyPressTime(); // unsigned @@ -119,9 +129,10 @@ class InputDevice : public QObject void activatePossiblePendingEvents(); void activatePossibleControlStickEvents(); // InputDeviceStick class void activatePossibleAxisEvents(); // InputDeviceAxis class - void activatePossibleDPadEvents(); // InputDeviceHat class - void activatePossibleVDPadEvents(); // InputDeviceVDPad class - void activatePossibleButtonEvents(); // InputDeviceButton class + void activatePossibleSensorEvents(); + void activatePossibleDPadEvents(); // InputDeviceHat class + void activatePossibleVDPadEvents(); // InputDeviceVDPad class + void activatePossibleButtonEvents(); // InputDeviceButton class void convertToUniqueMappSett(QSettings *sett, QString gUIDmappGroupSett, QString uniqueIDGroupSett); // bool isEmptyGUID(QString tempGUID); @@ -138,6 +149,8 @@ class InputDevice : public QObject SDL_Joystick *getJoyHandle() const; void applyStickCalibration(int index, double offsetX, double gainX, double offsetY, double gainY); + void applyAccelerometerCalibration(double offsetX, double offsetY, double offsetZ); + void applyGyroscopeCalibration(double offsetX, double offsetY, double offsetZ); protected: void enableSetConnections(SetJoystick *setstick); @@ -181,6 +194,8 @@ class InputDevice : public QObject int mode); // InputDeviceAxisBtn class void changeSetStickButtonAssociation(int button_index, int stick_index, int originset, int newset, int mode); // InputDeviceStick class + void changeSetSensorButtonAssociation(JoySensorDirection direction, JoySensorType type, int originset, int newset, + int mode); void changeSetDPadButtonAssociation(int button_index, int dpad_index, int originset, int newset, int mode); // InputDeviceHat class void changeSetVDPadButtonAssociation(int button_index, int dpad_index, int originset, int newset, @@ -212,15 +227,19 @@ class InputDevice : public QObject virtual void dpadButtonUpEvent(int setindex, int dpadindex, int buttonindex); // InputDeviceHat class virtual void stickButtonDownEvent(int setindex, int stickindex, int buttonindex); // InputDeviceStick class virtual void stickButtonUpEvent(int setindex, int stickindex, int buttonindex); // InputDeviceStick class + virtual void sensorButtonDownEvent(int setindex, JoySensorType type, JoySensorDirection direction); + virtual void sensorButtonUpEvent(int setindex, JoySensorType type, JoySensorDirection direction); void updateSetButtonNames(int index); // InputDeviceButton class void updateSetAxisButtonNames(int axisIndex, int buttonIndex); // InputDeviceAxis class void updateSetStickButtonNames(int stickIndex, int buttonIndex); // InputDeviceStick class + void updateSetSensorButtonNames(JoySensorType type, JoySensorDirection direction); void updateSetDPadButtonNames(int dpadIndex, int buttonIndex); // InputDeviceHat class void updateSetVDPadButtonNames(int vdpadIndex, int buttonIndex); // InputDeviceVDPad class void updateSetAxisNames(int axisIndex); // InputDeviceAxis class void updateSetStickNames(int stickIndex); // InputDeviceStick class + void updateSetSensorNames(JoySensorType type); void updateSetDPadNames(int dpadIndex); // InputDeviceHat class void updateSetVDPadNames(int vdpadIndex); // InputDeviceVDPad class diff --git a/src/inputdevicebitarraystatus.cpp b/src/inputdevicebitarraystatus.cpp index c1f9503be..dd3a19cba 100644 --- a/src/inputdevicebitarraystatus.cpp +++ b/src/inputdevicebitarraystatus.cpp @@ -70,6 +70,9 @@ InputDeviceBitArrayStatus::InputDeviceBitArrayStatus(InputDevice *device, bool r getButtonStatusLocal().setBit(i, button->getButtonState()); } } + + m_sensor_status.resize(SENSOR_COUNT); + m_sensor_status.fill(0); } void InputDeviceBitArrayStatus::changeAxesStatus(int axisIndex, bool value) @@ -96,10 +99,18 @@ void InputDeviceBitArrayStatus::changeHatStatus(int hatIndex, bool value) } } +void InputDeviceBitArrayStatus::changeSensorStatus(int sensorIndex, bool value) +{ + if ((sensorIndex >= 0) && (sensorIndex <= m_sensor_status.size())) + { + m_sensor_status.setBit(sensorIndex, value); + } +} + QBitArray InputDeviceBitArrayStatus::generateFinalBitArray() { int totalArraySize = 0; - totalArraySize = axesStatus.size() + hatButtonStatus.size() + getButtonStatusLocal().size(); + totalArraySize = axesStatus.size() + hatButtonStatus.size() + getButtonStatusLocal().size() + m_sensor_status.size(); QBitArray aggregateBitArray(totalArraySize, false); int currentBit = 0; @@ -121,6 +132,12 @@ QBitArray InputDeviceBitArrayStatus::generateFinalBitArray() currentBit++; } + for (int i = 0; i < SENSOR_COUNT; i++) + { + aggregateBitArray.setBit(currentBit, m_sensor_status.at(i)); + currentBit++; + } + return aggregateBitArray; } @@ -133,6 +150,7 @@ void InputDeviceBitArrayStatus::clearStatusValues() hatButtonStatus.replace(i, false); getButtonStatusLocal().fill(false); + m_sensor_status.fill(false); } QBitArray &InputDeviceBitArrayStatus::getButtonStatusLocal() { return buttonStatus; } diff --git a/src/inputdevicebitarraystatus.h b/src/inputdevicebitarraystatus.h index 073f9bfff..3cfb27587 100644 --- a/src/inputdevicebitarraystatus.h +++ b/src/inputdevicebitarraystatus.h @@ -35,6 +35,7 @@ class InputDeviceBitArrayStatus : public QObject void changeAxesStatus(int axisIndex, bool value); void changeButtonStatus(int buttonIndex, bool value); void changeHatStatus(int hatIndex, bool value); + void changeSensorStatus(int sensorIndex, bool value); QBitArray generateFinalBitArray(); void clearStatusValues(); @@ -45,6 +46,7 @@ class InputDeviceBitArrayStatus : public QObject QList axesStatus; QList hatButtonStatus; QBitArray buttonStatus; + QBitArray m_sensor_status; }; #endif // INPUTDEVICESTATUSEVENT_H diff --git a/src/joyaccelerometersensor.cpp b/src/joyaccelerometersensor.cpp new file mode 100644 index 000000000..b499950d7 --- /dev/null +++ b/src/joyaccelerometersensor.cpp @@ -0,0 +1,235 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define _USE_MATH_DEFINES + +#include "joyaccelerometersensor.h" +#include "globalvariables.h" +#include "joybuttontypes/joyaccelerometerbutton.h" + +#include + +const double JoyAccelerometerSensor::SHOCK_DETECT_THRESHOLD = 20.0; +const double JoyAccelerometerSensor::SHOCK_SUPPRESS_FACTOR = 0.5; +const double JoyAccelerometerSensor::SHOCK_TAU = 0.05; + +JoyAccelerometerSensor::JoyAccelerometerSensor(double rate, int originset, SetJoystick *parent_set, QObject *parent) + : JoySensor(ACCELEROMETER, originset, parent_set, parent) + , m_shock_filter(SHOCK_TAU, rate) +{ + reset(); + populateButtons(); + m_rate = qFuzzyIsNull(rate) ? PT1Filter::FALLBACK_RATE : rate; +} + +JoyAccelerometerSensor::~JoyAccelerometerSensor() {} + +/** + * @brief Get the value for the corresponding X axis. + * @return X axis value in m/s^2 + */ +float JoyAccelerometerSensor::getXCoordinate() const { return m_current_value[0]; } + +/** + * @brief Get the value for the corresponding Y axis. + * @return Y axis value in m/s^2 + */ +float JoyAccelerometerSensor::getYCoordinate() const { return m_current_value[1]; } + +/** + * @brief Get the value for the corresponding Z axis. + * @return Z axis value in m/s^2 + */ +float JoyAccelerometerSensor::getZCoordinate() const { return m_current_value[2]; } + +/** + * @brief Get the translated sensor type name + * @returns Translated sensor type name + */ +QString JoyAccelerometerSensor::sensorTypeName() const { return tr("Accelerometer"); } + +/** + * @brief Reads the calibration values of the sensor + * @param[out] offsetX Offset angle around the X axis + * @param[out] offsetY Offset angle around the Y axis + * @param[out] offsetZ Offset angle around the Z axis + */ +void JoyAccelerometerSensor::getCalibration(double *offsetX, double *offsetY, double *offsetZ) const +{ + *offsetX = m_calibration_value[0]; + *offsetY = m_calibration_value[1]; + *offsetZ = m_calibration_value[2]; +} + +/** + * @brief Sets the sensor calibration values and sets the calibration flag. + * @param[in] offsetX Offset angle around the X axis + * @param[in] offsetY Offset angle around the Y axis + * @param[in] offsetZ Offset angle around the Z axis + * + * This stores the orientation vector to store the calibration data later and + * calculates the neutral position rotation matrix from the orientation + * vector. + */ +void JoyAccelerometerSensor::setCalibration(double offsetX, double offsetY, double offsetZ) +{ + m_calibration_value[0] = offsetX; + m_calibration_value[1] = offsetY; + m_calibration_value[2] = offsetZ; + + double rad = sqrt(offsetX * offsetX + offsetY * offsetY + offsetZ * offsetZ); + double syz = sqrt(offsetY * offsetY + offsetZ * offsetZ); + + m_calibration_matrix[0][0] = syz / rad; + m_calibration_matrix[0][1] = -offsetX * offsetY / syz / rad; + m_calibration_matrix[0][2] = -offsetX * offsetZ / syz / rad; + m_calibration_matrix[1][0] = 0; + m_calibration_matrix[1][1] = -offsetZ / syz; + m_calibration_matrix[1][2] = offsetY / syz; + m_calibration_matrix[2][0] = -offsetX / rad; + m_calibration_matrix[2][1] = -offsetY / rad; + m_calibration_matrix[2][2] = -offsetZ / rad; + + m_calibrated = true; +} + +/** + * @brief Resets internal variables back to default + */ +void JoyAccelerometerSensor::reset() +{ + JoySensor::reset(); + m_max_zone = degToRad(GlobalVariables::JoySensor::ACCEL_MAX); + + m_shock_filter.reset(); + m_shock_suppress_count = 0; +} + +/** + * @brief Initializes the JoySensorButton objects for this sensor. + */ +void JoyAccelerometerSensor::populateButtons() +{ + JoySensorButton *button = nullptr; + button = new JoyAccelerometerButton(this, SENSOR_LEFT, m_originset, getParentSet(), this); + m_buttons.insert(SENSOR_LEFT, button); + + button = new JoyAccelerometerButton(this, SENSOR_RIGHT, m_originset, getParentSet(), this); + m_buttons.insert(SENSOR_RIGHT, button); + + button = new JoyAccelerometerButton(this, SENSOR_UP, m_originset, getParentSet(), this); + m_buttons.insert(SENSOR_UP, button); + + button = new JoyAccelerometerButton(this, SENSOR_DOWN, m_originset, getParentSet(), this); + m_buttons.insert(SENSOR_DOWN, button); + + button = new JoyAccelerometerButton(this, SENSOR_BWD, m_originset, getParentSet(), this); + m_buttons.insert(SENSOR_BWD, button); +} + +/** + * @brief Find the direction zone of the current sensor position. + * + * First, the pitch and roll angles on the unit sphere are calculated. + * Then, the unit sphere is divided into direction zones with the following algorithm: + * - Mark a spherical layer around the X axis at +/- the diagonal zone angle + * divided by two (called "range" in the code) + * - Generate another spherical layers by rotating the first layer around the Y axis. + * A third layer is not necessary because there are only two degrees of freedom. + * Check if a point is within each layer by comparing the absolute values + * of pitch and roll angles against the "range". + * If a point is in only one layer, it is in the orthogonal zone of one axis. + * If a point is in both or no zones, it is diagonal to both axes. + * There are two cases here because the spherical layers overlap if the diagonal + * angle is larger then 45 degree. + * + * Perform shock detection by taking the first order lag filtered absolute sum of + * all axes from "joyEvent" and apply a threshold. Discard some samples after + * the shock is over to avoid spurious pitch/roll events. + * + * @returns JoySensorDirection bitfield for the current direction zone. + */ +JoySensorDirection JoyAccelerometerSensor::calculateSensorDirection() +{ + double abs_sum = abs(m_current_value[0]) + abs(m_current_value[1]) + abs(m_current_value[2]); + if (m_shock_filter.process(abs_sum) > SHOCK_DETECT_THRESHOLD) + { + m_shock_suppress_count = m_rate * SHOCK_SUPPRESS_FACTOR; + return SENSOR_BWD; + } else if (m_shock_suppress_count != 0) + { + --m_shock_suppress_count; + return SENSOR_CENTERED; + } + + double pitch = calculatePitch(); + double roll = calculateRoll(); + double pitch_abs = abs(pitch); + double roll_abs = abs(roll); + if (pitch_abs * pitch_abs + roll_abs * roll_abs < m_dead_zone * m_dead_zone) + return SENSOR_CENTERED; + + double range = M_PI / 4 - m_diagonal_range / 2; + bool inPitch = pitch_abs < range; + bool inRoll = roll_abs < range; + + if (inPitch && !inRoll) + { + if (roll > 0) + return SENSOR_LEFT; + else + return SENSOR_RIGHT; + } else if (!inPitch && inRoll) + { + if (pitch > 0) + return SENSOR_UP; + else + return SENSOR_DOWN; + } else // in both or in none + { + if (pitch > 0) + { + if (roll > 0) + return SENSOR_LEFT_UP; + else + return SENSOR_RIGHT_UP; + } else + { + if (roll > 0) + return SENSOR_LEFT_DOWN; + else + return SENSOR_RIGHT_DOWN; + } + } +} + +/** + * @brief Applies calibration to queued input values + * + * This rotates the sensor coordinate system with the precalculated neutral + * position rotation matrix. + */ +void JoyAccelerometerSensor::applyCalibration() +{ + double x = m_pending_value[0]; + double y = m_pending_value[1]; + double z = m_pending_value[2]; + + m_pending_value[0] = m_calibration_matrix[0][0] * x + m_calibration_matrix[0][1] * y + m_calibration_matrix[0][2] * z; + m_pending_value[1] = m_calibration_matrix[1][0] * x + m_calibration_matrix[1][1] * y + m_calibration_matrix[1][2] * z; + m_pending_value[2] = m_calibration_matrix[2][0] * x + m_calibration_matrix[2][1] * y + m_calibration_matrix[2][2] * z; +} diff --git a/src/joyaccelerometersensor.h b/src/joyaccelerometersensor.h new file mode 100644 index 000000000..6b9a929cb --- /dev/null +++ b/src/joyaccelerometersensor.h @@ -0,0 +1,57 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "joysensor.h" + +class SetJoystick; + +/** + * @brief Represents an accelerometer sensor. + */ +class JoyAccelerometerSensor : public JoySensor +{ + public: + explicit JoyAccelerometerSensor(double rate, int originset, SetJoystick *parent_set, QObject *parent); + virtual ~JoyAccelerometerSensor(); + + virtual float getXCoordinate() const override; + virtual float getYCoordinate() const override; + virtual float getZCoordinate() const override; + virtual QString sensorTypeName() const override; + + virtual void getCalibration(double *offsetX, double *offsetY, double *offsetZ) const override; + virtual void setCalibration(double offsetX, double offsetY, double offsetZ) override; + + public slots: + virtual void reset() override; + + protected: + static const double SHOCK_DETECT_THRESHOLD; + static const double SHOCK_SUPPRESS_FACTOR; + static const double SHOCK_TAU; + + virtual void populateButtons() override; + virtual JoySensorDirection calculateSensorDirection() override; + virtual void applyCalibration() override; + + double m_rate; + PT1Filter m_shock_filter; + size_t m_shock_suppress_count; + double m_calibration_matrix[3][3]; +}; diff --git a/src/joybutton.cpp b/src/joybutton.cpp index 382a92c40..326fc6da1 100644 --- a/src/joybutton.cpp +++ b/src/joybutton.cpp @@ -168,6 +168,10 @@ void JoyButton::vdpadPassEvent(bool pressed, bool ignoresets) } } +/** + * @brief Activates mapped slots and generates QT events + * which highlight pressed controller buttons. + */ void JoyButton::joyEvent(bool pressed, bool ignoresets) { if (Logger::isDebugEnabled()) @@ -2388,7 +2392,7 @@ QList const &JoyButton::getActiveSlots() { return activeSlots; void JoyButton::setMouseSpeedX(int speed) { - if ((speed >= 1) && (speed <= 300)) + if ((speed >= 1) && (speed <= GlobalVariables::JoyButton::MAXMOUSESPEED)) { mouseSpeedX = speed; emit propertyUpdated(); @@ -2399,7 +2403,7 @@ int JoyButton::getMouseSpeedX() { return mouseSpeedX; } void JoyButton::setMouseSpeedY(int speed) { - if ((speed >= 1) && (speed <= 300)) + if ((speed >= 1) && (speed <= GlobalVariables::JoyButton::MAXMOUSESPEED)) { mouseSpeedY = speed; emit propertyUpdated(); @@ -2459,6 +2463,10 @@ void JoyButton::setChangeSetCondition(SetChangeCondition condition, bool passive JoyButton::SetChangeCondition JoyButton::getChangeSetCondition() { return setSelectionCondition; } +/** + * @brief Checks if this button is currently active + * @returns True if the button is pressed, false otherwise + */ bool JoyButton::getButtonState() { return isButtonPressed; } int JoyButton::getOriginSet() { return m_originset; } @@ -3476,7 +3484,7 @@ bool JoyButton::isDefault() value = value && (setSelectionCondition == DEFAULTSETCONDITION); value = value && (getAssignedSlots()->isEmpty()); value = value && (mouseMode == DEFAULTMOUSEMODE); - value = value && (mouseCurve == DEFAULTMOUSECURVE); + value = value && (mouseCurve == getDefaultMouseCurve()); value = value && (springWidth == GlobalVariables::JoyButton::DEFAULTSPRINGWIDTH); value = value && (springHeight == GlobalVariables::JoyButton::DEFAULTSPRINGHEIGHT); value = value && qFuzzyCompare(sensitivity, GlobalVariables::JoyButton::DEFAULTSENSITIVITY); @@ -3499,6 +3507,13 @@ bool JoyButton::isDefault() return value; } +/** + * @brief Returns the default mouse curve for this JoyButton type. + * Can be overwritten by subclasses. + * @returns Default mouse curve + */ +JoyButton::JoyMouseCurve JoyButton::getDefaultMouseCurve() const { return DEFAULTMOUSECURVE; } + void JoyButton::setIgnoreEventState(bool ignore) { ignoreEvents = ignore; } bool JoyButton::getIgnoreEventState() { return ignoreEvents; } @@ -3728,6 +3743,11 @@ void JoyButton::moveMouseCursor(int &movedX, int &movedY, int &movedElapsed, QLi cursorYSpeeds->clear(); } +/** + * @brief Combines mouse movement distances from multiple mouse mappings. + * @param[in,out] finalAx Combined mouse distance from previous iteration. Updated by this function. + * @param[in] infoAx Next mouse event to join into finalAx. + */ void JoyButton::distanceForMovingAx(double &finalAx, mouseCursorInfo infoAx) { if (!qFuzzyIsNull(infoAx.code)) @@ -4335,7 +4355,7 @@ void JoyButton::resetAllProperties() wheelSpeedX = GlobalVariables::JoyButton::DEFAULTWHEELX; wheelSpeedY = GlobalVariables::JoyButton::DEFAULTWHEELY; mouseMode = DEFAULTMOUSEMODE; - mouseCurve = DEFAULTMOUSECURVE; + mouseCurve = getDefaultMouseCurve(); springWidth = GlobalVariables::JoyButton::DEFAULTSPRINGWIDTH; springHeight = GlobalVariables::JoyButton::DEFAULTSPRINGHEIGHT; sensitivity = GlobalVariables::JoyButton::DEFAULTSENSITIVITY; diff --git a/src/joybutton.h b/src/joybutton.h index 06e9c9a94..37de8e3ee 100644 --- a/src/joybutton.h +++ b/src/joybutton.h @@ -38,6 +38,11 @@ class QXmlStreamWriter; // class QThread; class QThreadPool; +/** + * @brief Represents a button mapping in a SetJoystick + * Contains multiple JoyButtonSlots which do the actual mapping. + * Also has various static methods for mouse cursor movement. + */ class JoyButton : public QObject { Q_OBJECT @@ -162,7 +167,9 @@ class JoyButton : public QObject virtual bool isPartRealAxis(); virtual bool isModifierButton(); - virtual bool isDefault(); + bool isDefault(); + + virtual JoyMouseCurve getDefaultMouseCurve() const; virtual int getRealJoyNumber() const; diff --git a/src/joybuttontypes/joyaccelerometerbutton.cpp b/src/joybuttontypes/joyaccelerometerbutton.cpp new file mode 100644 index 000000000..21bd2da50 --- /dev/null +++ b/src/joybuttontypes/joyaccelerometerbutton.cpp @@ -0,0 +1,63 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "joyaccelerometerbutton.h" +#include "joysensordirection.h" + +JoyAccelerometerButton::JoyAccelerometerButton(JoySensor *sensor, int index, int originset, SetJoystick *parentSet, + QObject *parent) + : JoySensorButton(sensor, index, originset, parentSet, parent) +{ +} + +/** + * @brief Get translated accelerometer direction name of this button + * @returns Translated direction name + */ +QString JoyAccelerometerButton::getDirectionName() const +{ + QString label = QString(); + + switch (m_index) + { + case JoySensorDirection::SENSOR_UP: + label.append(tr("Up")); + break; + + case JoySensorDirection::SENSOR_DOWN: + label.append(tr("Down")); + break; + + case JoySensorDirection::SENSOR_LEFT: + label.append(tr("Left")); + break; + + case JoySensorDirection::SENSOR_RIGHT: + label.append(tr("Right")); + break; + + case JoySensorDirection::SENSOR_BWD: + label.append(tr("Shock")); + break; + + default: + WARN() << "unknown accelerometer direction"; + break; + } + + return label; +} diff --git a/src/joybuttontypes/joyaccelerometerbutton.h b/src/joybuttontypes/joyaccelerometerbutton.h new file mode 100644 index 000000000..d77b0a071 --- /dev/null +++ b/src/joybuttontypes/joyaccelerometerbutton.h @@ -0,0 +1,32 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include "joybuttontypes/joysensorbutton.h" + +/** + * @brief Represents an accelerometer mapping in a SetJoystick + */ +class JoyAccelerometerButton : public JoySensorButton +{ + Q_OBJECT + + public: + explicit JoyAccelerometerButton(JoySensor *sensor, int index, int originset, SetJoystick *parentSet, QObject *parent); + + virtual QString getDirectionName() const override; +}; diff --git a/src/joybuttontypes/joygyroscopebutton.cpp b/src/joybuttontypes/joygyroscopebutton.cpp new file mode 100644 index 000000000..b058c0180 --- /dev/null +++ b/src/joybuttontypes/joygyroscopebutton.cpp @@ -0,0 +1,66 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "joygyroscopebutton.h" +#include "joysensordirection.h" + +JoyGyroscopeButton::JoyGyroscopeButton(JoySensor *sensor, int index, int originset, SetJoystick *parentSet, QObject *parent) + : JoySensorButton(sensor, index, originset, parentSet, parent) +{ +} + +/** + * @brief Get translated gyroscope direction name of this button + * @returns Translated direction name + */ +QString JoyGyroscopeButton::getDirectionName() const +{ + QString label = QString(); + + switch (m_index) + { + case JoySensorDirection::SENSOR_UP: + label.append(tr("Pitch Up")); + break; + + case JoySensorDirection::SENSOR_DOWN: + label.append(tr("Pitch Down")); + break; + + case JoySensorDirection::SENSOR_LEFT: + label.append(tr("Yaw Left")); + break; + + case JoySensorDirection::SENSOR_RIGHT: + label.append(tr("Yaw Right")); + break; + + case JoySensorDirection::SENSOR_FWD: + label.append(tr("Roll Left")); + break; + + case JoySensorDirection::SENSOR_BWD: + label.append(tr("Roll Right")); + break; + + default: + WARN() << "unknown gyroscope direction"; + break; + } + + return label; +} diff --git a/src/joybuttontypes/joygyroscopebutton.h b/src/joybuttontypes/joygyroscopebutton.h new file mode 100644 index 000000000..0df666ed2 --- /dev/null +++ b/src/joybuttontypes/joygyroscopebutton.h @@ -0,0 +1,32 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include "joybuttontypes/joysensorbutton.h" + +/** + * @brief Represents a gyroscope mapping in a SetJoystick + */ +class JoyGyroscopeButton : public JoySensorButton +{ + Q_OBJECT + + public: + explicit JoyGyroscopeButton(JoySensor *sensor, int index, int originset, SetJoystick *parentSet, QObject *parent); + + virtual QString getDirectionName() const override; +}; diff --git a/src/joybuttontypes/joysensorbutton.cpp b/src/joybuttontypes/joysensorbutton.cpp new file mode 100644 index 000000000..5625b569d --- /dev/null +++ b/src/joybuttontypes/joysensorbutton.cpp @@ -0,0 +1,157 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "joysensorbutton.h" + +#include "event.h" +#include "globalvariables.h" +#include "joybutton.h" +#include "joysensor.h" +#include "setjoystick.h" +#include "vdpad.h" + +#include + +#include + +JoySensorButton::JoySensorButton(JoySensor *sensor, int index, int originset, SetJoystick *parentSet, QObject *parent) + : JoyGradientButton(index, originset, parentSet, parent) + , m_sensor(sensor) +{ + // Must be called here since virtual functions calls are disabled in constructors. + setMouseCurve(getDefaultMouseCurve()); +} + +/** + * @brief Get a 0 indexed number of button + * @return 0 indexed button index number + */ +int JoySensorButton::getRealJoyNumber() const { return m_index; } + +/** + * @brief Get the name of the button. + * Shows the sensor direction name instead of a button number. + * @returns Button name + */ +QString JoySensorButton::getPartialName(bool forceFullFormat, bool displayNames) const +{ + QString temp = m_sensor->getPartialName(forceFullFormat, displayNames); + temp.append(": "); + + if (!buttonName.isEmpty() && displayNames) + { + if (forceFullFormat) + temp.append(tr("Button")).append(" "); + temp.append(buttonName); + } else if (!defaultButtonName.isEmpty()) + { + if (forceFullFormat) + temp.append(tr("Button")).append(" "); + temp.append(defaultButtonName); + } else + { + temp.append(tr("Button")).append(" "); + temp.append(getDirectionName()); + } + + return temp; +} + +/** + * @brief Get the XML tag name of this button type + */ +QString JoySensorButton::getXmlName() { return GlobalVariables::JoySensorButton::xmlName; } + +/** + * @brief Get the distance that an element is away from its assigned dead zone + * @return Distance away from dead zone + */ +double JoySensorButton::getDistanceFromDeadZone() +{ + return m_sensor->calculateDirectionalDistance(static_cast(m_index)); +} + +/** + * @brief Get the distance factor that should be used for mouse movement + * @return Distance factor that should be used for mouse movement + */ +double JoySensorButton::getMouseDistanceFromDeadZone() +{ + return m_sensor->calculateDirectionalDistance(static_cast(m_index)); +} + +void JoySensorButton::setChangeSetCondition(SetChangeCondition condition, bool passive, bool updateActiveString) +{ + Q_UNUSED(updateActiveString); + SetChangeCondition oldCondition = setSelectionCondition; + + if ((condition != setSelectionCondition) && !passive) + { + if ((condition == SetChangeWhileHeld) || (condition == SetChangeTwoWay)) + { + // Set new condition + emit setAssignmentChanged(static_cast(m_index), m_sensor->getType(), setSelection, + condition); + } else if ((setSelectionCondition == SetChangeWhileHeld) || (setSelectionCondition == SetChangeTwoWay)) + { + // Remove old condition + emit setAssignmentChanged(static_cast(m_index), m_sensor->getType(), setSelection, + SetChangeDisabled); + } + + setSelectionCondition = condition; + } else if (passive) + { + setSelectionCondition = condition; + } + + if (setSelectionCondition == SetChangeDisabled) + { + setChangeSetSelection(-1); + } + + if (setSelectionCondition != oldCondition) + { + buildActiveZoneSummaryString(); + emit propertyUpdated(); + } +} + +/** + * @brief Check if button should be considered a part of a real controller + * axis. Needed for some dialogs so the program won't have to resort to + * type checking. + * @return Status of being part of a real controller axis + */ +bool JoySensorButton::isPartRealAxis() { return false; } + +/** + * @brief Returns the default mouse curve for JoySensorButtons. + * @returns Default mouse curve + */ +JoyButton::JoyMouseCurve JoySensorButton::getDefaultMouseCurve() const { return LinearCurve; } + +/** + * @brief Get the JoySensor associated to this button. + * @returns JoySensor + */ +JoySensor *JoySensorButton::getSensor() const { return m_sensor; } + +/** + * @brief returns the direction of this button. + */ +JoySensorDirection JoySensorButton::getDirection() const { return static_cast(m_index); } diff --git a/src/joybuttontypes/joysensorbutton.h b/src/joybuttontypes/joysensorbutton.h new file mode 100644 index 000000000..355f37e15 --- /dev/null +++ b/src/joybuttontypes/joysensorbutton.h @@ -0,0 +1,58 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include "joybuttontypes/joygradientbutton.h" +#include "joysensordirection.h" +#include "joysensortype.h" +#include "logger.h" + +class SetJoystick; +class JoySensor; + +/** + * @brief Represents a sensor mapping in a SetJoystick + */ +class JoySensorButton : public JoyGradientButton +{ + Q_OBJECT + + public: + explicit JoySensorButton(JoySensor *sensor, int index, int originset, SetJoystick *parentSet, QObject *parent); + + virtual int getRealJoyNumber() const override; + virtual QString getPartialName(bool forceFullFormat = false, bool displayNames = false) const override; + virtual QString getXmlName() override; + + virtual double getDistanceFromDeadZone() override; + virtual double getMouseDistanceFromDeadZone() override; + + virtual void setChangeSetCondition(SetChangeCondition condition, bool passive = false, + bool updateActiveString = true) override; + virtual bool isPartRealAxis() override; + virtual JoyMouseCurve getDefaultMouseCurve() const override; + + JoySensor *getSensor() const; + virtual QString getDirectionName() const = 0; + JoySensorDirection getDirection() const; + + signals: + void setAssignmentChanged(JoySensorDirection direction, JoySensorType type, int associated_set, int mode); + + private: + JoySensor *m_sensor; +}; diff --git a/src/joycontrolstickcontextmenu.h b/src/joycontrolstickcontextmenu.h index d03706885..df271613f 100644 --- a/src/joycontrolstickcontextmenu.h +++ b/src/joycontrolstickcontextmenu.h @@ -26,6 +26,9 @@ class JoyControlStick; class QWidget; +/** + * @brief The control stick context menu widget used by StickPushButtonGroup + */ class JoyControlStickContextMenu : public QMenu { Q_OBJECT diff --git a/src/joygyroscopesensor.cpp b/src/joygyroscopesensor.cpp new file mode 100644 index 000000000..23ba3d369 --- /dev/null +++ b/src/joygyroscopesensor.cpp @@ -0,0 +1,261 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define _USE_MATH_DEFINES + +#include "joygyroscopesensor.h" +#include "globalvariables.h" +#include "joybuttontypes/joygyroscopebutton.h" + +#include + +JoyGyroscopeSensor::JoyGyroscopeSensor(int originset, SetJoystick *parent_set, QObject *parent) + : JoySensor(GYROSCOPE, originset, parent_set, parent) +{ + reset(); + populateButtons(); +} + +JoyGyroscopeSensor::~JoyGyroscopeSensor() {} + +/** + * @brief Get the value for the corresponding X axis. + * @return X axis value in °/s + */ +float JoyGyroscopeSensor::getXCoordinate() const { return radToDeg(m_current_value[0]); } + +/** + * @brief Get the value for the corresponding Y axis. + * @return Y axis value in °/s + */ +float JoyGyroscopeSensor::getYCoordinate() const { return radToDeg(m_current_value[1]); } + +/** + * @brief Get the value for the corresponding Z axis. + * @return Z axis value in °/s + */ +float JoyGyroscopeSensor::getZCoordinate() const { return radToDeg(m_current_value[2]); } + +/** + * @brief Get the translated sensor type name + * @returns Translated sensor type name + */ +QString JoyGyroscopeSensor::sensorTypeName() const { return tr("Gyroscope"); } + +/** + * @brief Reads the calibration values of the sensor + * @param[out] offsetX Offset value for X axis + * @param[out] offsetY Offset value for Y axis + * @param[out] offsetZ Offset value for Z axis + */ +void JoyGyroscopeSensor::getCalibration(double *offsetX, double *offsetY, double *offsetZ) const +{ + *offsetX = m_calibration_value[0]; + *offsetY = m_calibration_value[1]; + *offsetZ = m_calibration_value[2]; +} + +/** + * @brief Sets the sensor calibration values and sets the calibration flag. + * @param[in] offsetX Offset value for X axis + * @param[in] offsetY Offset value for Y axis + * @param[in] offsetZ Offset value for Z axis + */ +void JoyGyroscopeSensor::setCalibration(double offsetX, double offsetY, double offsetZ) +{ + m_calibration_value[0] = offsetX; + m_calibration_value[1] = offsetY; + m_calibration_value[2] = offsetZ; + m_calibrated = true; +} + +/** + * @brief Resets internal variables back to default + */ +void JoyGyroscopeSensor::reset() +{ + JoySensor::reset(); + m_max_zone = degToRad(GlobalVariables::JoySensor::GYRO_MAX); +} + +/** + * @brief Initializes the JoySensorButton objects for this sensor. + */ +void JoyGyroscopeSensor::populateButtons() +{ + JoySensorButton *button = nullptr; + button = new JoyGyroscopeButton(this, SENSOR_LEFT, m_originset, getParentSet(), this); + m_buttons.insert(SENSOR_LEFT, button); + + button = new JoyGyroscopeButton(this, SENSOR_RIGHT, m_originset, getParentSet(), this); + m_buttons.insert(SENSOR_RIGHT, button); + + button = new JoyGyroscopeButton(this, SENSOR_UP, m_originset, getParentSet(), this); + m_buttons.insert(SENSOR_UP, button); + + button = new JoyGyroscopeButton(this, SENSOR_DOWN, m_originset, getParentSet(), this); + m_buttons.insert(SENSOR_DOWN, button); + + button = new JoyGyroscopeButton(this, SENSOR_FWD, m_originset, getParentSet(), this); + m_buttons.insert(SENSOR_FWD, button); + + button = new JoyGyroscopeButton(this, SENSOR_BWD, m_originset, getParentSet(), this); + m_buttons.insert(SENSOR_BWD, button); +} + +/** + * @brief Applies calibration to queued input values + */ +void JoyGyroscopeSensor::applyCalibration() +{ + m_pending_value[0] -= m_calibration_value[0]; + m_pending_value[1] -= m_calibration_value[1]; + m_pending_value[2] -= m_calibration_value[2]; +} + +/** + * @brief Find the direction zone of the current sensor position. + * + * First, the sensor axis values are normalized so they are on the unit sphere. + * Then, the unit sphere is divided into direction zones with the following algorithm: + * - Mark a spherical layer around the X axis at +/- the diagonal zone angle + * divided by two (called "range" in the code) + * Then generate two more spherical layers by rotating the + * first layer around the Y and Z axes. + * Check if a point is within each layer by comparing the absolute value + * of each coordinate against the "range". + * If a point is in only one layer, it is in the diagonal zone between two axes. + * If a point is in two layers, it is in the orthogonal zone of one axis. + * If a point is in three or zero zones, it is diagonal to all three axes. + * There are two cases here because the spherical layers overlap if the diagonal + * angle is larger then 45 degree. + * + * @returns JoySensorDirection bitfield for the current direction zone. + */ +JoySensorDirection JoyGyroscopeSensor::calculateSensorDirection() +{ + double distance = calculateDistance(); + if (distance < m_dead_zone) + return SENSOR_CENTERED; + + double range = sin(M_PI / 4 - m_diagonal_range / 2); + double normPitch = m_current_value[0] / distance; + double normRoll = m_current_value[1] / distance; + double normYaw = m_current_value[2] / distance; + + bool inPitch = abs(normPitch) < range; + bool inRoll = abs(normRoll) < range; + bool inYaw = abs(normYaw) < range; + + if (inPitch && !inRoll && !inYaw) + { + if (normRoll > 0) + { + if (normYaw > 0) + return SENSOR_RIGHT_FWD; + else + return SENSOR_LEFT_FWD; + } else + { + if (normYaw > 0) + return SENSOR_RIGHT_BWD; + else + return SENSOR_LEFT_BWD; + } + } else if (!inPitch && inRoll && !inYaw) + { + if (normPitch > 0) + { + if (normYaw > 0) + return SENSOR_RIGHT_UP; + else + return SENSOR_LEFT_UP; + } else + { + if (normYaw > 0) + return SENSOR_RIGHT_DOWN; + else + return SENSOR_LEFT_DOWN; + } + } else if (!inPitch && !inRoll && inYaw) + { + if (normPitch > 0) + { + if (normRoll > 0) + return SENSOR_UP_FWD; + else + return SENSOR_UP_BWD; + } else + { + if (normRoll > 0) + return SENSOR_DOWN_FWD; + else + return SENSOR_DOWN_BWD; + } + } else if (inPitch && inRoll && !inYaw) + { + if (normYaw > 0) + return SENSOR_RIGHT; + else + return SENSOR_LEFT; + } else if (inPitch && !inRoll && inYaw) + { + if (normRoll > 0) + return SENSOR_FWD; + else + return SENSOR_BWD; + } else if (!inPitch && inRoll && inYaw) + { + if (normPitch > 0) + return SENSOR_UP; + else + return SENSOR_DOWN; + } else // in all or in none + { + if (normPitch > 0) + { + if (normRoll > 0) + { + if (normYaw > 0) + return SENSOR_RIGHT_UP_FWD; + else + return SENSOR_LEFT_UP_FWD; + } else + { + if (normYaw > 0) + return SENSOR_RIGHT_UP_BWD; + else + return SENSOR_LEFT_UP_BWD; + } + } else + { + if (normRoll > 0) + { + if (normYaw > 0) + return SENSOR_RIGHT_DOWN_FWD; + else + return SENSOR_LEFT_DOWN_FWD; + } else + { + if (normYaw > 0) + return SENSOR_RIGHT_DOWN_BWD; + else + return SENSOR_LEFT_DOWN_BWD; + } + } + } +} diff --git a/src/joygyroscopesensor.h b/src/joygyroscopesensor.h new file mode 100644 index 000000000..965e88489 --- /dev/null +++ b/src/joygyroscopesensor.h @@ -0,0 +1,48 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "joysensor.h" + +class SetJoystick; + +/** + * @brief Represents a gyroscope sensor. + */ +class JoyGyroscopeSensor : public JoySensor +{ + public: + explicit JoyGyroscopeSensor(int originset, SetJoystick *parent_set, QObject *parent); + virtual ~JoyGyroscopeSensor(); + + virtual float getXCoordinate() const override; + virtual float getYCoordinate() const override; + virtual float getZCoordinate() const override; + virtual QString sensorTypeName() const override; + + virtual void getCalibration(double *offsetX, double *offsetY, double *offsetZ) const override; + virtual void setCalibration(double offsetX, double offsetY, double offsetZ) override; + + public slots: + virtual void reset() override; + + protected: + virtual void populateButtons(); + virtual JoySensorDirection calculateSensorDirection() override; + virtual void applyCalibration() override; +}; diff --git a/src/joysensor.cpp b/src/joysensor.cpp new file mode 100644 index 000000000..7141d7245 --- /dev/null +++ b/src/joysensor.cpp @@ -0,0 +1,849 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define _USE_MATH_DEFINES + +#include "joysensor.h" +#include "inputdevice.h" +#include "joybuttontypes/joysensorbutton.h" +#include "xml/joybuttonxml.h" + +#include +#include +#include + +JoySensor::JoySensor(JoySensorType type, int originset, SetJoystick *parent_set, QObject *parent) + : QObject(parent) + , m_type(type) + , m_calibrated(false) + , m_pending_event(false) + , m_originset(originset) + , m_parent_set(parent_set) +{ + reset(); + + m_delay_timer.setSingleShot(true); + connect(&m_delay_timer, &QTimer::timeout, this, &JoySensor::delayTimerExpired); +} + +JoySensor::~JoySensor() {} + +/** + * @brief Main sensor mapping function. + * When activated, it generates a "moved" QT event which updates various parts of the UI. + * Furthermore, it controls the sensor delay timer and calculates the current sensor + * direction and generates "active" and "released" QT events which enable/disable + * button highlights in the GUI. + * Finally, it calls createDeskEvent if the active/released button state has changed. + */ +void JoySensor::joyEvent(float *values, bool ignoresets) +{ + m_current_value[0] = values[0]; + m_current_value[1] = values[1]; + m_current_value[2] = values[2]; + + JoySensorDirection pending_direction = calculateSensorDirection(); + + if (pending_direction != SENSOR_CENTERED && !m_active) + { + m_active = true; + emit active(m_current_value[0], m_current_value[1], m_current_value[2]); + + if (ignoresets || (m_sensor_delay == 0)) + { + if (m_delay_timer.isActive()) + m_delay_timer.stop(); + createDeskEvent(pending_direction, ignoresets); + } else + { + if (!m_delay_timer.isActive()) + m_delay_timer.start(m_sensor_delay); + } + } else if (pending_direction == SENSOR_CENTERED && m_active) + { + m_active = false; + emit released(m_current_value[0], m_current_value[1], m_current_value[2]); + + if (ignoresets || (m_sensor_delay == 0)) + { + if (m_delay_timer.isActive()) + m_delay_timer.stop(); + createDeskEvent(pending_direction, ignoresets); + } else + { + if (!m_delay_timer.isActive()) + m_delay_timer.start(m_sensor_delay); + } + } else if (m_active) + { + if (ignoresets || (m_sensor_delay == 0)) + { + if (m_delay_timer.isActive()) + m_delay_timer.stop(); + + createDeskEvent(pending_direction, ignoresets); + } else + { + if (m_current_direction != pending_direction) + { + if (!m_delay_timer.isActive()) + m_delay_timer.start(m_sensor_delay); + } else + { + if (m_delay_timer.isActive()) + m_delay_timer.stop(); + + createDeskEvent(pending_direction, ignoresets); + } + } + } + + emit moved(m_current_value[0], m_current_value[1], m_current_value[2]); +} + +/** + * @brief Queues next movement event from InputDaemon + */ +void JoySensor::queuePendingEvent(float *values, bool ignoresets) +{ + m_pending_value[0] = values[0]; + m_pending_value[1] = values[1]; + m_pending_value[2] = values[2]; + + if (m_calibrated) + applyCalibration(); + + m_pending_event = true; + m_pending_ignore_sets = ignoresets; +} + +/** + * @brief Activates previously queued movement event + * This is called by InputDevice. + */ +void JoySensor::activatePendingEvent() +{ + if (!m_pending_event) + return; + + joyEvent(m_pending_value, m_pending_ignore_sets); + + clearPendingEvent(); +} + +/** + * @brief Checks if an event is queued + * @returns True if an event is queued, false otherwise. + */ +bool JoySensor::hasPendingEvent() const { return m_pending_event; } + +/** + * @brief Clears a previously queued event + */ +void JoySensor::clearPendingEvent() +{ + m_pending_event = false; + m_pending_ignore_sets = false; +} + +/** + * @brief Copy slots from all sensor buttons and properties from a sensor + * onto another. + * @param JoySensor object to be modified. + */ +void JoySensor::copyAssignments(JoySensor *dest_sensor) +{ + dest_sensor->reset(); + dest_sensor->m_dead_zone = m_dead_zone; + dest_sensor->m_max_zone = m_max_zone; + dest_sensor->m_diagonal_range = m_diagonal_range; + dest_sensor->m_sensor_name = m_sensor_name; + dest_sensor->m_sensor_delay = m_sensor_delay; + + dest_sensor->m_calibrated = m_calibrated; + dest_sensor->m_calibration_value[0] = m_calibration_value[0]; + dest_sensor->m_calibration_value[1] = m_calibration_value[1]; + dest_sensor->m_calibration_value[2] = m_calibration_value[2]; + + auto dest_buttons = dest_sensor->getButtons(); + for (auto iter = dest_buttons->begin(); iter != dest_buttons->end(); ++iter) + { + JoySensorButton *dest_button = iter.value(); + if (dest_button != nullptr) + { + JoySensorButton *source_button = m_buttons.value(dest_button->getDirection()); + if (source_button != nullptr) + source_button->copyAssignments(dest_button); + } + } + + if (!dest_sensor->isDefault()) + emit propertyUpdated(); +} + +/** + * @brief Check if any direction is mapped to a keyboard or mouse event + * @returns True if a mapping exists, false otherwise + */ +bool JoySensor::hasSlotsAssigned() const +{ + for (const auto &button : m_buttons) + { + if (button != nullptr) + { + if (button->getAssignedSlots()->count() > 0) + return true; + } + } + return false; +} + +/** + * @brief Get the name of this sensor + * @returns Sensor name + */ +QString JoySensor::getPartialName(bool forceFullFormat, bool displayNames) const +{ + QString label = QString(); + + if (!m_sensor_name.isEmpty() && displayNames) + { + if (forceFullFormat) + { + label.append(sensorTypeName()).append(" "); + } + + label.append(m_sensor_name); + } else + { + label.append(sensorTypeName()).append(" "); + } + + return label; +} + +/** + * @brief Returns the sensor name + */ +QString JoySensor::getSensorName() const { return m_sensor_name; } + +/** + * @brief Returns the sensor type + */ +JoySensorType JoySensor::getType() const { return m_type; } + +/** + * @brief Returns the current sensor direction + */ +JoySensorDirection JoySensor::getCurrentDirection() const { return m_current_direction; } + +/** + * @brief Get the assigned dead zone value + * @return Assigned dead zone value in degree or degree/s + */ +double JoySensor::getDeadZone() const { return radToDeg(m_dead_zone); } + +/** + * @brief Get the assigned diagonal range value + * @return Assigned diagonal range in degree or degree/s + */ +double JoySensor::getDiagonalRange() const { return radToDeg(m_diagonal_range); } + +/** + * @brief Get the assigned max zone value + * @return Assigned max zone value in degree or degree/s + */ +double JoySensor::getMaxZone() const { return radToDeg(m_max_zone); } + +/** + * @brief Get the assigned input delay + * @returns Input delay in ms + */ +unsigned int JoySensor::getSensorDelay() const { return m_sensor_delay; } + +/** + * @brief Checks if the sensor vector is currently in the dead zone + * @returns True if it is in the dead zone, false otherwise + */ +bool JoySensor::inDeadZone(float *values) const { return calculateDistance(values[0], values[1], values[2]) < m_dead_zone; } + +/** + * @brief Get current radial distance of the sensor past the assigned + * dead zone. + * @return Distance between 0 and max zone in radiants. + */ +double JoySensor::getDistanceFromDeadZone() const +{ + return getDistanceFromDeadZone(m_current_value[0], m_current_value[1], m_current_value[2]); +} + +/** + * @brief Get radial distance of the sensor past the assigned dead zone + * based on the passed X, Y and Z axes values associated with the sensor. + * @param X axis value + * @param Y axis value + * @param Z axis value + * @return Distance between 0 and max zone in radiants. + */ +double JoySensor::getDistanceFromDeadZone(double x, double y, double z) const +{ + double distance = calculateDistance(x, y, z); + return qBound(0.0, distance - m_dead_zone, m_max_zone); +} + +/** + * @brief Get current X distance of the sensor past the assigned + * dead zone. + * @return Distance between 0 and max zone in radiants. + */ +double JoySensor::calculateXDistanceFromDeadZone() const +{ + return calculateXDistanceFromDeadZone(m_current_value[0], m_current_value[1], m_current_value[2]); +} + +/** + * @brief Get current X distance of the sensor past the assigned + * dead zone based on the passed X, Y and Z axes values associated + * with the sensor. The algorithm checks if an axis parallel line + * through the current sensor position intersects with the dead zone + * sphere and subtracts the line segment within the sphere from the + * distance. The resulting value is not normalized because there is no + * practical maximum value for a sensor as you can always move it a bit faster. + * @param X axis value + * @param Y axis value + * @param Z axis value + * @return Distance between 0 and max zone in radiants. + */ +double JoySensor::calculateXDistanceFromDeadZone(double x, double y, double z) const +{ + double discriminant = m_dead_zone * m_dead_zone - y * y - z * z; + if (discriminant <= 0) + return std::min(abs(x), m_max_zone); + else + return std::min(abs(x) - sqrt(discriminant), m_max_zone); +} + +/** + * @brief Get current Y distance of the sensor past the assigned + * dead zone. + * @return Distance between 0 and max zone in radiants. + */ +double JoySensor::calculateYDistanceFromDeadZone() const +{ + return calculateYDistanceFromDeadZone(m_current_value[0], m_current_value[1], m_current_value[2]); +} + +/** + * @brief Get current Y distance of the sensor past the assigned + * dead zone based on the passed X, Y and Z axes values associated + * with the sensor. The algorithm checks if an axis parallel line + * through the current sensor position intersects with the dead zone + * sphere and subtracts the line segment within the sphere from the + * distance. The resulting value is not normalized because there is no + * practical maximum value for a sensor as you can always move it a bit faster. + * @param X axis value + * @param Y axis value + * @param Z axis value + * @return Distance between 0 and max zone in radiants. + */ +double JoySensor::calculateYDistanceFromDeadZone(double x, double y, double z) const +{ + double discriminant = m_dead_zone * m_dead_zone - x * x - z * z; + if (discriminant <= 0) + return std::min(abs(y), m_max_zone); + else + return std::min(abs(y) - sqrt(discriminant), m_max_zone); +} + +/** + * @brief Get current Z distance of the sensor past the assigned + * dead zone. + * @return Distance between 0 and max zone in radiants. + */ +double JoySensor::calculateZDistanceFromDeadZone() const +{ + return calculateZDistanceFromDeadZone(m_current_value[0], m_current_value[1], m_current_value[2]); +} + +/** + * @brief Get current Z distance of the sensor past the assigned + * dead zone based on the passed X, Y and Z axes values associated + * with the sensor. The algorithm checks if an axis parallel line + * through the current sensor position intersects with the dead zone + * sphere and subtracts the line segment within the sphere from the + * distance. The resulting value is not normalized because there is no + * practical maximum value for a sensor as you can always move it a bit faster. + * @param X axis value + * @param Y axis value + * @param Z axis value + * @return Distance between 0 and max zone in radiants. + */ +double JoySensor::calculateZDistanceFromDeadZone(double x, double y, double z) const +{ + double discriminant = m_dead_zone * m_dead_zone - x * x - y * y; + if (discriminant <= 0) + return std::min(abs(z), m_max_zone); + else + return std::min(abs(z) - sqrt(discriminant), m_max_zone); +} + +/** + * @brief Get the vector length of the sensor + * @return Vector length + */ +double JoySensor::calculateDistance() const +{ + return calculateDistance(m_current_value[0], m_current_value[1], m_current_value[2]); +} + +/** + * @brief Get the vector length of the sensor based on the passed + * X, Y and Z axes values associated with the sensor. + * @return Vector length + */ +double JoySensor::calculateDistance(double x, double y, double z) const { return sqrt(x * x + y * y + z * z); } + +/** + * @brief Calculate the pitch angle (in degrees) corresponding to the current + * position of controller. + * @return Pitch (in degrees) + */ +double JoySensor::calculatePitch() const +{ + return calculatePitch(m_current_value[0], m_current_value[1], m_current_value[2]); +} + +/** + * @brief Calculate the pitch angle (in degrees) corresponding to the current + * passed X, Y and Z axes values associated with the sensor. + * position of controller. + * See https://www.nxp.com/files-static/sensors/doc/app_note/AN3461.pdf + * for a description of the used algorithm. + * @param X axis value + * @param Y axis value + * @param Z axis value + * @return Pitch (in degrees) + */ +double JoySensor::calculatePitch(double x, double y, double z) const +{ + double rad = calculateDistance(x, y, z); + double pitch = -atan2(z / rad, y / rad) - M_PI / 2; + if (pitch < -M_PI) + pitch += 2 * M_PI; + return pitch; +} + +/** + * @brief Calculate the roll angle (in degrees) corresponding to the current + * position of controller. + * @return Roll (in degrees) + */ +double JoySensor::calculateRoll() const { return calculateRoll(m_current_value[0], m_current_value[1], m_current_value[2]); } + +/** + * @brief Calculate the roll angle (in degrees) corresponding to the current + * passed X, Y and Z axes values associated with the sensor. + * position of controller. + * See https://www.nxp.com/files-static/sensors/doc/app_note/AN3461.pdf + * for a description of the used algorithm. + * @param X axis value + * @param Y axis value + * @param Z axis value + * @return Roll (in degrees) + */ +double JoySensor::calculateRoll(double x, double y, double z) const +{ + double rad = calculateDistance(x, y, z); + + double xp, yp, zp; + xp = x / rad; + yp = y / rad; + zp = z / rad; + double roll = atan2(sqrt(yp * yp + zp * zp), -xp) - M_PI / 2; + if (roll < -M_PI) + roll += 2 * M_PI; + return roll; +} + +/** + * @brief Used to calculate the distance value that should be used by + * the JoyButton in the given direction. + * @param direction + * @return Distance factor that should be used by JoySensorButton + */ +double JoySensor::calculateDirectionalDistance(JoySensorDirection direction) const +{ + double finalDistance = 0.0; + + switch (direction) + { + case JoySensorDirection::SENSOR_LEFT: + case JoySensorDirection::SENSOR_RIGHT: + // Yaw + finalDistance = calculateZDistanceFromDeadZone(); + break; + case JoySensorDirection::SENSOR_UP: + case JoySensorDirection::SENSOR_DOWN: + // Pitch + finalDistance = calculateXDistanceFromDeadZone(); + break; + case JoySensorDirection::SENSOR_FWD: + case JoySensorDirection::SENSOR_BWD: + // Roll + finalDistance = calculateYDistanceFromDeadZone(); + break; + default: + break; + } + + return finalDistance; +} + +/** + * @brief Utility function which converts a given value from radians to degree. + */ +double JoySensor::radToDeg(double value) { return value * 180 / M_PI; } + +/** + * @brief Utility function which converts a given value from degree to radians. + */ +double JoySensor::degToRad(double value) { return value * M_PI / 180; } + +/** + * @brief Check if the sensor is calibrated + * @returns True if it is calibrated, false otherwise. + */ +bool JoySensor::isCalibrated() const { return m_calibrated; } + +/** + * @brief Resets the calibration of the sensor back to uncalibrated state. + */ +void JoySensor::resetCalibration() { m_calibrated = false; } + +/** + * @brief Returns a QHash which maps the SensorDirection to + * the corresponding JoySensorButton. + */ +QHash *JoySensor::getButtons() { return &m_buttons; } + +/** + * @brief Get a pointer to the sensor direction button for the desired + * direction. + * @param Value of the direction of the sensor. + * @return Pointer to the sensor direction button for the sensor + * direction. + */ +JoySensorButton *JoySensor::getDirectionButton(JoySensorDirection direction) { return m_buttons.value(direction); } + +/** + * @brief Checks if all sensor settings and button mappings are the their default values. + * This is used during XML serialization to skip unnecessary objects. + * @returns True if everything is at the default, false otherwise. + */ +bool JoySensor::isDefault() const +{ + bool value = true; + value = value && qFuzzyCompare(getDeadZone(), GlobalVariables::JoySensor::DEFAULTDEADZONE); + if (m_type == ACCELEROMETER) + value = value && qFuzzyCompare(getMaxZone(), GlobalVariables::JoySensor::ACCEL_MAX); + else + value = value && qFuzzyCompare(getMaxZone(), GlobalVariables::JoySensor::GYRO_MAX); + + value = value && qFuzzyCompare(getDiagonalRange(), GlobalVariables::JoySensor::DEFAULTDIAGONALRANGE); + value = value && (m_sensor_delay == GlobalVariables::JoySensor::DEFAULTSENSORDELAY); + + for (const auto &button : m_buttons) + value = value && (button->isDefault()); + + return value; +} + +/** + * @brief Resets internal variables back to default + */ +void JoySensor::reset() +{ + m_active = false; + for (size_t i = 0; i < ACTIVE_BUTTON_COUNT; ++i) + m_active_button[i] = nullptr; + + m_dead_zone = degToRad(GlobalVariables::JoySensor::DEFAULTDEADZONE); + m_diagonal_range = degToRad(GlobalVariables::JoySensor::DEFAULTDIAGONALRANGE); + m_pending_event = false; + + m_current_direction = JoySensorDirection::SENSOR_CENTERED; + m_sensor_name.clear(); + m_sensor_delay = GlobalVariables::JoySensor::DEFAULTSENSORDELAY; + + resetButtons(); +} + +/** + * @brief Sets the dead zone of the sensor to the given value + * @param[in] value New sensor dead zone + */ +void JoySensor::setDeadZone(double value) +{ + value = abs(degToRad(value)); + if (!qFuzzyCompare(value, m_dead_zone) && (value <= m_max_zone)) + { + m_dead_zone = value; + emit deadZoneChanged(value); + emit propertyUpdated(); + } +} + +/** + * @brief Sets the maximum zone of the sensor to the given value + * @param[in] value New sensor maximum zone + */ +void JoySensor::setMaxZone(double value) +{ + value = abs(degToRad(value)); + if (!qFuzzyCompare(value, m_max_zone) && (value > m_dead_zone)) + { + m_max_zone = value; + emit maxZoneChanged(value); + emit propertyUpdated(); + } +} + +/** + * @brief Set the diagonal range value for a sensor. + * @param Value between 1 - 90. + */ +void JoySensor::setDiagonalRange(double value) +{ + if (value < 1) + value = 1; + else if (value > 90) + value = 90; + + value = degToRad(value); + if (!qFuzzyCompare(value, m_diagonal_range)) + { + m_diagonal_range = value; + emit diagonalRangeChanged(value); + emit propertyUpdated(); + } +} + +/** + * @brief Sets the sensor input delaqy to the given value + * @param[in] value New sensor input delay in ms + */ +void JoySensor::setSensorDelay(unsigned int value) +{ + if (((value >= 10) && (value <= 1000)) || (value == 0)) + { + m_sensor_delay = value; + emit sensorDelayChanged(value); + emit propertyUpdated(); + } +} + +/** + * @brief Sets the name of this sensor + * @param[in] tempName New sensor name + */ +void JoySensor::setSensorName(QString tempName) +{ + if ((tempName.length() <= 20) && (tempName != m_sensor_name)) + { + m_sensor_name = tempName; + emit sensorNameChanged(); + } +} + +void JoySensor::establishPropertyUpdatedConnection() +{ + connect(this, &JoySensor::propertyUpdated, getParentSet()->getInputDevice(), &InputDevice::profileEdited); +} + +/** + * @brief Take a XML stream and set the sensor and direction button properties + * according to the values contained within the stream. + * @param QXmlStreamReader instance that will be used to read property values. + */ +void JoySensor::readConfig(QXmlStreamReader *xml) +{ + if (xml->isStartElement() && (xml->name() == "sensor")) + { + xml->readNextStartElement(); + + while (!xml->atEnd() && (!xml->isEndElement() && (xml->name() != "sensor"))) + { + if ((xml->name() == "deadZone") && xml->isStartElement()) + { + QString temptext = xml->readElementText(); + float tempchoice = temptext.toFloat(); + setDeadZone(tempchoice); + } else if ((xml->name() == "maxZone") && xml->isStartElement()) + { + QString temptext = xml->readElementText(); + float tempchoice = temptext.toFloat(); + setMaxZone(tempchoice); + } else if ((xml->name() == "diagonalRange") && xml->isStartElement()) + { + QString temptext = xml->readElementText(); + int tempchoice = temptext.toInt(); + setDiagonalRange(tempchoice); + } else if ((xml->name() == GlobalVariables::JoySensorButton::xmlName) && xml->isStartElement()) + { + int index = xml->attributes().value("index").toString().toInt(); + JoySensorButton *button = m_buttons.value(static_cast(index)); + QPointer joyButtonXml = new JoyButtonXml(button); + + if (button != nullptr) + joyButtonXml->readConfig(xml); + else + xml->skipCurrentElement(); + + if (!joyButtonXml.isNull()) + delete joyButtonXml; + } else if ((xml->name() == "sensorDelay") && xml->isStartElement()) + { + QString temptext = xml->readElementText(); + int tempchoice = temptext.toInt(); + setSensorDelay(tempchoice); + } else + { + xml->skipCurrentElement(); + } + + xml->readNextStartElement(); + } + } +} + +/** + * @brief Write the status of the properties of a sensor and direction buttons + * to an XML stream. + * @param QXmlStreamWriter instance that will be used to write a profile. + */ +void JoySensor::writeConfig(QXmlStreamWriter *xml) const +{ + if (!isDefault()) + { + xml->writeStartElement("sensor"); + xml->writeAttribute("type", QString::number(m_type)); + + if (!qFuzzyCompare(getDeadZone(), GlobalVariables::JoySensor::DEFAULTDEADZONE)) + xml->writeTextElement("deadZone", QString::number(getDeadZone())); + + if (!qFuzzyCompare(getMaxZone(), (m_type == ACCELEROMETER ? GlobalVariables::JoySensor::ACCEL_MAX + : GlobalVariables::JoySensor::GYRO_MAX))) + xml->writeTextElement("maxZone", QString::number(getMaxZone())); + + if (!qFuzzyCompare(getDiagonalRange(), GlobalVariables::JoySensor::DEFAULTDIAGONALRANGE)) + xml->writeTextElement("diagonalRange", QString::number(getDiagonalRange())); + + if (m_sensor_delay > GlobalVariables::JoySensor::DEFAULTSENSORDELAY) + xml->writeTextElement("sensorDelay", QString::number(m_sensor_delay)); + + for (const auto &button : m_buttons) + { + JoyButtonXml *joyButtonXml = new JoyButtonXml(button); + joyButtonXml->writeConfig(xml); + delete joyButtonXml; + joyButtonXml = nullptr; + } + + xml->writeEndElement(); + } +} + +/** + * @brief Get pointer to the set that a sensor belongs to. + * @return Pointer to the set that a sensor belongs to. + */ +SetJoystick *JoySensor::getParentSet() const { return m_parent_set; } + +/** + * @brief Slot called when m_delay_timer has timed out. The method will + * call createDeskEvent. + */ +void JoySensor::delayTimerExpired() { createDeskEvent(calculateSensorDirection()); } + +/** + * @brief Reset all the properties of the sensor direction buttons. + */ +void JoySensor::resetButtons() +{ + for (const auto &button : m_buttons) + { + if (button != nullptr) + button->reset(); + } +} + +/** + * @brief Set buttons for current sensor direction zone. + * + * @param Pointer to an array of three JoySensorButton pointers in which + * the results are stored. + */ +void JoySensor::determineSensorEvent(JoySensorButton **eventbutton) const +{ + if (m_current_direction & SENSOR_LEFT) + eventbutton[0] = m_buttons.value(SENSOR_LEFT); + else if (m_current_direction & SENSOR_RIGHT) + eventbutton[0] = m_buttons.value(SENSOR_RIGHT); + + if (m_current_direction & SENSOR_UP) + eventbutton[1] = m_buttons.value(SENSOR_UP); + else if (m_current_direction & SENSOR_DOWN) + eventbutton[1] = m_buttons.value(SENSOR_DOWN); + + if (m_current_direction & SENSOR_FWD) + eventbutton[2] = m_buttons.value(SENSOR_FWD); + else if (m_current_direction & SENSOR_BWD) + eventbutton[2] = m_buttons.value(SENSOR_BWD); +} + +/** + * @brief Find the position of the three sensor axes, deactivate no longer used + * sensor direction button and then activate direction buttons for new + * direction. + * @param Should set changing operations be ignored. Necessary in the middle + * of a set change. + */ +void JoySensor::createDeskEvent(JoySensorDirection direction, bool ignoresets) +{ + m_current_direction = direction; + JoySensorButton *eventbutton[ACTIVE_BUTTON_COUNT] = {nullptr}; + determineSensorEvent(eventbutton); + + for (size_t i = 0; i < ACTIVE_BUTTON_COUNT; ++i) + { + if (m_active_button[i] != nullptr && m_active_button[i] != eventbutton[i]) + { + m_active_button[i]->joyEvent(false, ignoresets); + m_active_button[i] = nullptr; + } + + if (eventbutton[i] != nullptr && m_active_button[i] == nullptr) + { + m_active_button[i] = eventbutton[i]; + m_active_button[i]->joyEvent(true, ignoresets); + } else if (eventbutton[i] == nullptr && m_active_button[i] != nullptr) + { + m_active_button[i]->joyEvent(false, ignoresets); + m_active_button[i] = nullptr; + } + } +} diff --git a/src/joysensor.h b/src/joysensor.h new file mode 100644 index 000000000..bf6558e65 --- /dev/null +++ b/src/joysensor.h @@ -0,0 +1,159 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include +#include + +#include "joysensordirection.h" +#include "joysensortype.h" +#include "pt1filter.h" + +class SetJoystick; +class JoySensorButton; +class QXmlStreamReader; +class QXmlStreamWriter; + +/** + * @brief Represents one sensor in a SetJoystick and its connections to + * other parts of the application. + * Receives hardware input events from InputDaemon, processes them and + * generates GUI as well as Mouse+Keyboard events. + */ +class JoySensor : public QObject +{ + Q_OBJECT + + public: + explicit JoySensor(JoySensorType type, int originset, SetJoystick *parent_set, QObject *parent); + virtual ~JoySensor(); + + void joyEvent(float *values, bool ignoresets = false); + void queuePendingEvent(float *values, bool ignoresets = false); + void activatePendingEvent(); + bool hasPendingEvent() const; + void clearPendingEvent(); + + void copyAssignments(JoySensor *dest_sensor); + bool hasSlotsAssigned() const; + + QString getPartialName(bool forceFullFormat = false, bool displayNames = false) const; + QString getSensorName() const; + + JoySensorType getType() const; + JoySensorDirection getCurrentDirection() const; + double getDeadZone() const; + double getDiagonalRange() const; + double getMaxZone() const; + unsigned int getSensorDelay() const; + virtual float getXCoordinate() const = 0; + virtual float getYCoordinate() const = 0; + virtual float getZCoordinate() const = 0; + virtual QString sensorTypeName() const = 0; + + bool inDeadZone(float *values) const; + double getDistanceFromDeadZone() const; + double getDistanceFromDeadZone(double x, double y, double z) const; + double calculateXDistanceFromDeadZone() const; + double calculateXDistanceFromDeadZone(double x, double y, double z) const; + double calculateYDistanceFromDeadZone() const; + double calculateYDistanceFromDeadZone(double x, double y, double z) const; + double calculateZDistanceFromDeadZone() const; + double calculateZDistanceFromDeadZone(double x, double y, double z) const; + double calculateDistance() const; + double calculateDistance(double x, double y, double z) const; + double calculatePitch() const; + double calculatePitch(double x, double y, double z) const; + double calculateRoll() const; + double calculateRoll(double x, double y, double z) const; + double calculateDirectionalDistance(JoySensorDirection direction) const; + + static double radToDeg(double value); + static double degToRad(double value); + + bool isCalibrated() const; + void resetCalibration(); + virtual void getCalibration(double *offsetX, double *offsetY, double *offsetZ) const = 0; + virtual void setCalibration(double offsetX, double offsetY, double offsetZ) = 0; + + QHash *getButtons(); + JoySensorButton *getDirectionButton(JoySensorDirection direction); + + bool isDefault() const; + void readConfig(QXmlStreamReader *xml); + void writeConfig(QXmlStreamWriter *xml) const; + + SetJoystick *getParentSet() const; + + signals: + void moved(float xaxis, float yaxis, float zaxis); + void active(float xaxis, float yaxis, float zaxis); + void released(float xaxis, float yaxis, float zaxis); + void deadZoneChanged(double value); + void diagonalRangeChanged(double value); + void maxZoneChanged(double value); + void sensorDelayChanged(int value); + void sensorNameChanged(); + void propertyUpdated(); + + public slots: + virtual void reset(); + void setDeadZone(double value); + void setMaxZone(double value); + void setDiagonalRange(double value); + void setSensorDelay(unsigned int value); + void setSensorName(QString tempName); + void establishPropertyUpdatedConnection(); + + private slots: + void delayTimerExpired(); + + protected: + void resetButtons(); + virtual void populateButtons() = 0; + virtual JoySensorDirection calculateSensorDirection() = 0; + virtual void applyCalibration() = 0; + void determineSensorEvent(JoySensorButton **eventbutton) const; + void createDeskEvent(JoySensorDirection direction, bool ignoresets = false); + + JoySensorType m_type; + double m_dead_zone; + double m_diagonal_range; + double m_max_zone; + unsigned int m_sensor_delay; + + bool m_active; + static const size_t ACTIVE_BUTTON_COUNT = 3; + JoySensorButton *m_active_button[ACTIVE_BUTTON_COUNT]; + + float m_current_value[3]; + float m_pending_value[3]; + bool m_calibrated; + double m_calibration_value[3]; + bool m_pending_event; + bool m_pending_ignore_sets; + + int m_originset; + QString m_sensor_name; + QTimer m_delay_timer; + + JoySensorDirection m_current_direction; + SetJoystick *m_parent_set; + QHash m_buttons; +}; diff --git a/src/joysensorbuttonpushbutton.cpp b/src/joysensorbuttonpushbutton.cpp new file mode 100644 index 000000000..ecd1e8c9a --- /dev/null +++ b/src/joysensorbuttonpushbutton.cpp @@ -0,0 +1,117 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "joysensorbuttonpushbutton.h" + +#include "joybuttoncontextmenu.h" +#include "joybuttontypes/joysensorbutton.h" +#include "joysensor.h" + +#include +#include +#include + +JoySensorButtonPushButton::JoySensorButtonPushButton(JoySensorButton *button, bool displayNames, QWidget *parent) + : FlashButtonWidget(displayNames, parent) + , m_button(button) +{ + refreshLabel(); + enableFlashes(); + + tryFlash(); + + setContextMenuPolicy(Qt::CustomContextMenu); + connect(this, &JoySensorButtonPushButton::customContextMenuRequested, this, &JoySensorButtonPushButton::showContextMenu); + connect(m_button, &JoySensorButton::propertyUpdated, this, &JoySensorButtonPushButton::refreshLabel); + connect(m_button, &JoySensorButton::activeZoneChanged, this, &JoySensorButtonPushButton::refreshLabel); +} + +/** + * @brief Get the JoySensorButton for this mapping + */ +JoySensorButton *JoySensorButtonPushButton::getButton() { return m_button; } + +/** + * @brief Disables highlight when the sensor axis is moved + */ +void JoySensorButtonPushButton::disableFlashes() +{ + if (m_button != nullptr) + { + disconnect(m_button, &JoySensorButton::clicked, this, &JoySensorButtonPushButton::flash); + disconnect(m_button, &JoySensorButton::released, this, &JoySensorButtonPushButton::unflash); + } + unflash(); +} + +/** + * @brief Enables highlight when the sensor axis is moved + */ +void JoySensorButtonPushButton::enableFlashes() +{ + if (m_button != nullptr) + { + connect(m_button, &JoySensorButton::clicked, this, &JoySensorButtonPushButton::flash, Qt::QueuedConnection); + connect(m_button, &JoySensorButton::released, this, &JoySensorButtonPushButton::unflash, Qt::QueuedConnection); + } +} + +/** + * @brief Generate the string that will be displayed on the button + * @return Display string + */ +QString JoySensorButtonPushButton::generateLabel() +{ + QString temp = QString(); + if (m_button != nullptr) + { + if (!m_button->getActionName().isEmpty() && ifDisplayNames()) + { + qDebug() << "Action name was not empty"; + temp = m_button->getActionName().replace("&", "&&"); + + } else + { + qDebug() << "Action name was empty"; + temp = m_button->getCalculatedActiveZoneSummary().replace("&", "&&"); + } + } + + qDebug() << "Here is name of action for pushed sensor button: " << temp; + + return temp; +} + +/** + * @brief Shows sensor context menu + */ +void JoySensorButtonPushButton::showContextMenu(const QPoint &point) +{ + QPoint globalPos = mapToGlobal(point); + JoyButtonContextMenu *contextMenu = new JoyButtonContextMenu(m_button, this); + contextMenu->buildMenu(); + contextMenu->popup(globalPos); +} + +/** + * @brief Highlights the button when mapped button is active + */ +void JoySensorButtonPushButton::tryFlash() +{ + if (m_button != nullptr && m_button->getButtonState()) + flash(); +} diff --git a/src/joysensorbuttonpushbutton.h b/src/joysensorbuttonpushbutton.h new file mode 100644 index 000000000..49e315dfd --- /dev/null +++ b/src/joysensorbuttonpushbutton.h @@ -0,0 +1,50 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include "flashbuttonwidget.h" + +class JoySensorButton; +class QWidget; + +/** + * @brief A direction button in the SensorPushButtonGroup + */ +class JoySensorButtonPushButton : public FlashButtonWidget +{ + Q_OBJECT + Q_PROPERTY(bool isflashing READ isButtonFlashing) + + public: + explicit JoySensorButtonPushButton(JoySensorButton *button, bool displayNames, QWidget *parent = nullptr); + + JoySensorButton *getButton(); + void tryFlash(); + + protected: + virtual QString generateLabel() override; + + public slots: + void disableFlashes() override; + void enableFlashes() override; + + private slots: + void showContextMenu(const QPoint &point); + + private: + JoySensorButton *m_button; +}; diff --git a/src/joysensorcontextmenu.cpp b/src/joysensorcontextmenu.cpp new file mode 100644 index 000000000..00cbfc489 --- /dev/null +++ b/src/joysensorcontextmenu.cpp @@ -0,0 +1,76 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "joysensorcontextmenu.h" + +#include "common.h" +#include "inputdevice.h" +#include "joybuttontypes/joysensorbutton.h" +#include "joysensor.h" +#include "mousedialog/mousesensorsettingsdialog.h" + +#include +#include +#include + +JoySensorContextMenu::JoySensorContextMenu(JoySensor *sensor, QWidget *parent) + : QMenu(parent) + , m_sensor(sensor) + , m_preset(sensor) +{ + connect(this, &JoySensorContextMenu::aboutToHide, this, &JoySensorContextMenu::deleteLater); +} + +/** + * @brief Populates the context menu + */ +void JoySensorContextMenu::buildMenu() +{ + QAction *action; + QActionGroup *presetGroup = new QActionGroup(this); + JoySensorPreset::Preset currentPreset = m_preset.currentPreset(); + QList presets = m_preset.getAvailablePresets(); + + for (const auto &preset : presets) + { + action = addAction(m_preset.getPresetName(preset)); + action->setCheckable(true); + action->setChecked(currentPreset == preset); + action->setData(QVariant(preset)); + connect(action, &QAction::triggered, this, + [this, action] { m_preset.setSensorPreset(static_cast(action->data().toInt())); }); + presetGroup->addAction(action); + } + + if (m_sensor->getType() == GYROSCOPE) + { + addSeparator(); + + action = addAction(tr("Mouse Settings")); + action->setCheckable(false); + connect(action, &QAction::triggered, this, &JoySensorContextMenu::openMouseSettingsDialog); + } +} + +/** + * @brief Opens MouseSensorSettingsDialog from context menu + */ +void JoySensorContextMenu::openMouseSettingsDialog() +{ + MouseSensorSettingsDialog *dialog = new MouseSensorSettingsDialog(m_sensor, parentWidget()); + dialog->show(); +} diff --git a/src/joysensorcontextmenu.h b/src/joysensorcontextmenu.h new file mode 100644 index 000000000..5c8493e18 --- /dev/null +++ b/src/joysensorcontextmenu.h @@ -0,0 +1,43 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include "joysensorpreset.h" + +#include + +class JoySensor; +class QWidget; + +/** + * @brief The control stick context menu widget used by SensorPushButtonGroup + */ +class JoySensorContextMenu : public QMenu +{ + Q_OBJECT + + public: + explicit JoySensorContextMenu(JoySensor *sensor, QWidget *parent = nullptr); + void buildMenu(); + + private slots: + void openMouseSettingsDialog(); + + private: + JoySensor *m_sensor; + JoySensorPreset m_preset; +}; diff --git a/src/joysensordirection.h b/src/joysensordirection.h new file mode 100644 index 000000000..f88595488 --- /dev/null +++ b/src/joysensordirection.h @@ -0,0 +1,59 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include + +/** + * @brief A bitfield style enum which encodes all possible three dimensional + * sensor directions. This allows triggering the mapped buttons by simply + * checking the bits for the six basic directions. + */ +enum JoySensorDirection +{ + SENSOR_CENTERED = 0, + SENSOR_LEFT = (1 << 0), + SENSOR_RIGHT = (1 << 1), + SENSOR_UP = (1 << 2), + SENSOR_DOWN = (1 << 3), + SENSOR_FWD = (1 << 4), + SENSOR_BWD = (1 << 5), + + SENSOR_LEFT_UP = SENSOR_LEFT | SENSOR_UP, + SENSOR_LEFT_DOWN = SENSOR_LEFT | SENSOR_DOWN, + SENSOR_LEFT_FWD = SENSOR_LEFT | SENSOR_FWD, + SENSOR_LEFT_BWD = SENSOR_LEFT | SENSOR_BWD, + SENSOR_RIGHT_UP = SENSOR_RIGHT | SENSOR_UP, + SENSOR_RIGHT_DOWN = SENSOR_RIGHT | SENSOR_DOWN, + SENSOR_RIGHT_FWD = SENSOR_RIGHT | SENSOR_FWD, + SENSOR_RIGHT_BWD = SENSOR_RIGHT | SENSOR_BWD, + SENSOR_UP_FWD = SENSOR_UP | SENSOR_FWD, + SENSOR_UP_BWD = SENSOR_UP | SENSOR_BWD, + SENSOR_DOWN_FWD = SENSOR_DOWN | SENSOR_FWD, + SENSOR_DOWN_BWD = SENSOR_DOWN | SENSOR_BWD, + SENSOR_LEFT_UP_FWD = SENSOR_LEFT | SENSOR_UP | SENSOR_FWD, + SENSOR_LEFT_UP_BWD = SENSOR_LEFT | SENSOR_UP | SENSOR_BWD, + SENSOR_LEFT_DOWN_FWD = SENSOR_LEFT | SENSOR_DOWN | SENSOR_FWD, + SENSOR_LEFT_DOWN_BWD = SENSOR_LEFT | SENSOR_DOWN | SENSOR_BWD, + SENSOR_RIGHT_UP_FWD = SENSOR_RIGHT | SENSOR_UP | SENSOR_FWD, + SENSOR_RIGHT_UP_BWD = SENSOR_RIGHT | SENSOR_UP | SENSOR_BWD, + SENSOR_RIGHT_DOWN_FWD = SENSOR_RIGHT | SENSOR_DOWN | SENSOR_FWD, + SENSOR_RIGHT_DOWN_BWD = SENSOR_RIGHT | SENSOR_DOWN | SENSOR_BWD +}; + +Q_DECLARE_METATYPE(JoySensorDirection) diff --git a/src/joysensorfactory.cpp b/src/joysensorfactory.cpp new file mode 100644 index 000000000..052110659 --- /dev/null +++ b/src/joysensorfactory.cpp @@ -0,0 +1,33 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "joysensorfactory.h" +#include "joyaccelerometersensor.h" +#include "joygyroscopesensor.h" +#include "setjoystick.h" + +namespace JoySensorFactory { +JoySensor *build(JoySensorType type, double rate, int originset, SetJoystick *parent_set, QObject *parent) +{ + if (type == ACCELEROMETER) + return new JoyAccelerometerSensor(rate, originset, parent_set, parent); + else if (type == GYROSCOPE) + return new JoyGyroscopeSensor(originset, parent_set, parent); + else + return nullptr; +} +} // namespace JoySensorFactory diff --git a/src/joysensorfactory.h b/src/joysensorfactory.h new file mode 100644 index 000000000..671070883 --- /dev/null +++ b/src/joysensorfactory.h @@ -0,0 +1,27 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "joysensortype.h" + +class JoySensor; +class SetJoystick; + +namespace JoySensorFactory { +JoySensor *build(JoySensorType type, double rate, int originset, SetJoystick *parent_set, QObject *parent); +} diff --git a/src/joysensorpreset.cpp b/src/joysensorpreset.cpp new file mode 100644 index 000000000..f4e793913 --- /dev/null +++ b/src/joysensorpreset.cpp @@ -0,0 +1,371 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "joysensorpreset.h" + +#include "antkeymapper.h" +#include "common.h" +#include "joybuttontypes/joysensorbutton.h" +#include "joysensor.h" + +JoySensorPreset::JoySensorPreset(JoySensor *sensor, QObject *parent) + : QObject(parent) + , m_sensor(sensor) + , m_helper(sensor) +{ + m_helper.moveToThread(m_sensor->thread()); +} + +/** + * @brief Get available presets for the underlying sensor type + * @returns QList of available presets + */ +QList JoySensorPreset::getAvailablePresets() +{ + QList result; + result.append(PRESET_NONE); + + if (m_sensor->getType() == ACCELEROMETER) + { + result.append(PRESET_ARROWS); + result.append(PRESET_WASD); + result.append(PRESET_NUMPAD); + } else + { + result.append(PRESET_MOUSE); + result.append(PRESET_MOUSE_INV_H); + result.append(PRESET_MOUSE_INV_V); + result.append(PRESET_MOUSE_INV_HV); + } + + return result; +} + +/** + * @brief Determines the preset used by the underlying sensor + * @returns The used preset if a preset is used or PRESET_NONE otherwise + */ +JoySensorPreset::Preset JoySensorPreset::currentPreset() +{ + Preset result = PRESET_NONE; + QList *leftslots, *rightslots, *upslots, *downslots, *fwdslots, *bwdslots; + JoySensorButton *leftButton, *rightButton, *upButton, *downButton, *fwdButton, *bwdButton; + + PadderCommon::inputDaemonMutex.lock(); + + if (m_sensor->getType() == GYROSCOPE) + { + leftButton = m_sensor->getDirectionButton(SENSOR_LEFT); + leftslots = leftButton->getAssignedSlots(); + rightButton = m_sensor->getDirectionButton(SENSOR_RIGHT); + rightslots = rightButton->getAssignedSlots(); + upButton = m_sensor->getDirectionButton(SENSOR_UP); + upslots = upButton->getAssignedSlots(); + downButton = m_sensor->getDirectionButton(SENSOR_DOWN); + downslots = downButton->getAssignedSlots(); + fwdButton = m_sensor->getDirectionButton(SENSOR_FWD); + fwdslots = fwdButton->getAssignedSlots(); + bwdButton = m_sensor->getDirectionButton(SENSOR_BWD); + bwdslots = bwdButton->getAssignedSlots(); + + if (upslots->length() == 1 && downslots->length() == 1 && leftslots->length() == 1 && rightslots->length() == 1 && + fwdslots->length() == 1 && bwdslots->length() == 1) + { + JoyButtonSlot *upslot = upslots->at(0); + JoyButtonSlot *downslot = downslots->at(0); + JoyButtonSlot *leftslot = leftslots->at(0); + JoyButtonSlot *rightslot = rightslots->at(0); + JoyButtonSlot *fwdslot = fwdslots->at(0); + JoyButtonSlot *bwdslot = bwdslots->at(0); + + if ((upslot->getSlotMode() == JoyButtonSlot::JoyMouseMovement) && + (upslot->getSlotCode() == JoyButtonSlot::MouseUp) && + (downslot->getSlotMode() == JoyButtonSlot::JoyMouseMovement) && + (downslot->getSlotCode() == JoyButtonSlot::MouseDown) && + (leftslot->getSlotMode() == JoyButtonSlot::JoyMouseMovement) && + (leftslot->getSlotCode() == JoyButtonSlot::MouseLeft) && + (rightslot->getSlotMode() == JoyButtonSlot::JoyMouseMovement) && + (rightslot->getSlotCode() == JoyButtonSlot::MouseRight) && + (fwdslot->getSlotCode() == JoyButtonSlot::MouseLeft) && + (bwdslot->getSlotCode() == JoyButtonSlot::MouseRight)) + { + result = PRESET_MOUSE; + } else if ((upslot->getSlotMode() == JoyButtonSlot::JoyMouseMovement) && + (upslot->getSlotCode() == JoyButtonSlot::MouseUp) && + (downslot->getSlotMode() == JoyButtonSlot::JoyMouseMovement) && + (downslot->getSlotCode() == JoyButtonSlot::MouseDown) && + (leftslot->getSlotMode() == JoyButtonSlot::JoyMouseMovement) && + (leftslot->getSlotCode() == JoyButtonSlot::MouseRight) && + (rightslot->getSlotMode() == JoyButtonSlot::JoyMouseMovement) && + (rightslot->getSlotCode() == JoyButtonSlot::MouseLeft) && + (fwdslot->getSlotCode() == JoyButtonSlot::MouseRight) && + (bwdslot->getSlotCode() == JoyButtonSlot::MouseLeft)) + { + result = PRESET_MOUSE_INV_H; + } else if ((upslot->getSlotMode() == JoyButtonSlot::JoyMouseMovement) && + (upslot->getSlotCode() == JoyButtonSlot::MouseDown) && + (downslot->getSlotMode() == JoyButtonSlot::JoyMouseMovement) && + (downslot->getSlotCode() == JoyButtonSlot::MouseUp) && + (leftslot->getSlotMode() == JoyButtonSlot::JoyMouseMovement) && + (leftslot->getSlotCode() == JoyButtonSlot::MouseLeft) && + (rightslot->getSlotMode() == JoyButtonSlot::JoyMouseMovement) && + (rightslot->getSlotCode() == JoyButtonSlot::MouseRight) && + (fwdslot->getSlotCode() == JoyButtonSlot::MouseLeft) && + (bwdslot->getSlotCode() == JoyButtonSlot::MouseRight)) + { + result = PRESET_MOUSE_INV_V; + } else if ((upslot->getSlotMode() == JoyButtonSlot::JoyMouseMovement) && + (upslot->getSlotCode() == JoyButtonSlot::MouseDown) && + (downslot->getSlotMode() == JoyButtonSlot::JoyMouseMovement) && + (downslot->getSlotCode() == JoyButtonSlot::MouseUp) && + (leftslot->getSlotMode() == JoyButtonSlot::JoyMouseMovement) && + (leftslot->getSlotCode() == JoyButtonSlot::MouseRight) && + (rightslot->getSlotMode() == JoyButtonSlot::JoyMouseMovement) && + (rightslot->getSlotCode() == JoyButtonSlot::MouseLeft) && + (fwdslot->getSlotCode() == JoyButtonSlot::MouseRight) && + (bwdslot->getSlotCode() == JoyButtonSlot::MouseLeft)) + { + result = PRESET_MOUSE_INV_HV; + } + } + } else + { + leftButton = m_sensor->getDirectionButton(SENSOR_LEFT); + leftslots = leftButton->getAssignedSlots(); + rightButton = m_sensor->getDirectionButton(SENSOR_RIGHT); + rightslots = rightButton->getAssignedSlots(); + upButton = m_sensor->getDirectionButton(SENSOR_UP); + upslots = upButton->getAssignedSlots(); + downButton = m_sensor->getDirectionButton(SENSOR_DOWN); + downslots = downButton->getAssignedSlots(); + bwdButton = m_sensor->getDirectionButton(SENSOR_BWD); + bwdslots = bwdButton->getAssignedSlots(); + + if (upslots->length() == 1 && downslots->length() == 1 && leftslots->length() == 1 && rightslots->length() == 1 && + bwdslots->length() == 0) + { + JoyButtonSlot *upslot = upslots->at(0); + JoyButtonSlot *downslot = downslots->at(0); + JoyButtonSlot *leftslot = leftslots->at(0); + JoyButtonSlot *rightslot = rightslots->at(0); + + if ((upslot->getSlotMode() == JoyButtonSlot::JoyKeyboard) && + (upslot->getSlotCode() == AntKeyMapper::getInstance()->returnVirtualKey(Qt::Key_Up)) && + (downslot->getSlotMode() == JoyButtonSlot::JoyKeyboard) && + (downslot->getSlotCode() == AntKeyMapper::getInstance()->returnVirtualKey(Qt::Key_Down)) && + (leftslot->getSlotMode() == JoyButtonSlot::JoyKeyboard) && + (leftslot->getSlotCode() == AntKeyMapper::getInstance()->returnVirtualKey(Qt::Key_Left)) && + (rightslot->getSlotMode() == JoyButtonSlot::JoyKeyboard) && + (rightslot->getSlotCode() == AntKeyMapper::getInstance()->returnVirtualKey(Qt::Key_Right))) + { + result = PRESET_ARROWS; + } else if ((upslot->getSlotMode() == JoyButtonSlot::JoyKeyboard) && + (upslot->getSlotCode() == AntKeyMapper::getInstance()->returnVirtualKey(Qt::Key_W)) && + (downslot->getSlotMode() == JoyButtonSlot::JoyKeyboard) && + (downslot->getSlotCode() == AntKeyMapper::getInstance()->returnVirtualKey(Qt::Key_S)) && + (leftslot->getSlotMode() == JoyButtonSlot::JoyKeyboard) && + (leftslot->getSlotCode() == AntKeyMapper::getInstance()->returnVirtualKey(Qt::Key_A)) && + (rightslot->getSlotMode() == JoyButtonSlot::JoyKeyboard) && + (rightslot->getSlotCode() == AntKeyMapper::getInstance()->returnVirtualKey(Qt::Key_D))) + { + result = PRESET_WASD; + } else if ((upslot->getSlotMode() == JoyButtonSlot::JoyKeyboard) && + (upslot->getSlotCode() == + AntKeyMapper::getInstance()->returnVirtualKey(QtKeyMapperBase::AntKey_KP_8)) && + (downslot->getSlotMode() == JoyButtonSlot::JoyKeyboard) && + (downslot->getSlotCode() == + AntKeyMapper::getInstance()->returnVirtualKey(QtKeyMapperBase::AntKey_KP_2)) && + (leftslot->getSlotMode() == JoyButtonSlot::JoyKeyboard) && + (leftslot->getSlotCode() == + AntKeyMapper::getInstance()->returnVirtualKey(QtKeyMapperBase::AntKey_KP_4)) && + (rightslot->getSlotMode() == JoyButtonSlot::JoyKeyboard) && + (rightslot->getSlotCode() == + AntKeyMapper::getInstance()->returnVirtualKey(QtKeyMapperBase::AntKey_KP_6))) + { + result = PRESET_NUMPAD; + } + } + } + + PadderCommon::inputDaemonMutex.unlock(); + return result; +} + +/** + * @brief Get the translated name of a given preset + * @returns Translated preset name + */ +QString JoySensorPreset::getPresetName(Preset preset) +{ + QString result; + switch (preset) + { + case PRESET_NONE: + result = tr("None"); + break; + case PRESET_MOUSE: + result = tr("Mouse (Normal)"); + break; + case PRESET_MOUSE_INV_H: + result = tr("Mouse (Inverted Horizontal)"); + break; + case PRESET_MOUSE_INV_V: + result = tr("Mouse (Inverted Vertical)"); + break; + case PRESET_MOUSE_INV_HV: + result = tr("Mouse (Inverted Horizontal + Vertical)"); + break; + case PRESET_ARROWS: + result = tr("Arrows"); + break; + case PRESET_WASD: + result = tr("Keys: W | A | S | D"); + break; + case PRESET_NUMPAD: + result = tr("NumPad"); + break; + } + return result; +} + +/** + * @brief Assigns given preset to the underlying sensor + * This function defines the preset mappings and sensor parameters. + * @param[in] preset The preset to assign + */ +void JoySensorPreset::setSensorPreset(Preset preset) +{ + JoyButtonSlot *leftButtonSlot = nullptr; + JoyButtonSlot *rightButtonSlot = nullptr; + JoyButtonSlot *upButtonSlot = nullptr; + JoyButtonSlot *downButtonSlot = nullptr; + JoyButtonSlot *fwdButtonSlot = nullptr; + JoyButtonSlot *bwdButtonSlot = nullptr; + + switch (preset) + { + case PRESET_NONE: + QMetaObject::invokeMethod(&m_helper, "clearButtonsSlotsEventReset"); + QMetaObject::invokeMethod(m_sensor, "setDiagonalRange", Q_ARG(double, 45)); + break; + + case PRESET_MOUSE: + leftButtonSlot = new JoyButtonSlot(JoyButtonSlot::MouseLeft, JoyButtonSlot::JoyMouseMovement, this); + rightButtonSlot = new JoyButtonSlot(JoyButtonSlot::MouseRight, JoyButtonSlot::JoyMouseMovement, this); + upButtonSlot = new JoyButtonSlot(JoyButtonSlot::MouseUp, JoyButtonSlot::JoyMouseMovement, this); + downButtonSlot = new JoyButtonSlot(JoyButtonSlot::MouseDown, JoyButtonSlot::JoyMouseMovement, this); + fwdButtonSlot = new JoyButtonSlot(JoyButtonSlot::MouseLeft, JoyButtonSlot::JoyMouseMovement, this); + bwdButtonSlot = new JoyButtonSlot(JoyButtonSlot::MouseRight, JoyButtonSlot::JoyMouseMovement, this); + QMetaObject::invokeMethod(m_sensor, "setDeadZone", Q_ARG(double, 0)); + QMetaObject::invokeMethod(m_sensor, "setDiagonalRange", Q_ARG(double, 90)); + break; + + case PRESET_MOUSE_INV_H: + leftButtonSlot = new JoyButtonSlot(JoyButtonSlot::MouseRight, JoyButtonSlot::JoyMouseMovement, this); + rightButtonSlot = new JoyButtonSlot(JoyButtonSlot::MouseLeft, JoyButtonSlot::JoyMouseMovement, this); + upButtonSlot = new JoyButtonSlot(JoyButtonSlot::MouseUp, JoyButtonSlot::JoyMouseMovement, this); + downButtonSlot = new JoyButtonSlot(JoyButtonSlot::MouseDown, JoyButtonSlot::JoyMouseMovement, this); + fwdButtonSlot = new JoyButtonSlot(JoyButtonSlot::MouseRight, JoyButtonSlot::JoyMouseMovement, this); + bwdButtonSlot = new JoyButtonSlot(JoyButtonSlot::MouseLeft, JoyButtonSlot::JoyMouseMovement, this); + QMetaObject::invokeMethod(m_sensor, "setDeadZone", Q_ARG(double, 0)); + QMetaObject::invokeMethod(m_sensor, "setDiagonalRange", Q_ARG(double, 90)); + break; + + case PRESET_MOUSE_INV_V: + leftButtonSlot = new JoyButtonSlot(JoyButtonSlot::MouseLeft, JoyButtonSlot::JoyMouseMovement, this); + rightButtonSlot = new JoyButtonSlot(JoyButtonSlot::MouseRight, JoyButtonSlot::JoyMouseMovement, this); + upButtonSlot = new JoyButtonSlot(JoyButtonSlot::MouseDown, JoyButtonSlot::JoyMouseMovement, this); + downButtonSlot = new JoyButtonSlot(JoyButtonSlot::MouseUp, JoyButtonSlot::JoyMouseMovement, this); + fwdButtonSlot = new JoyButtonSlot(JoyButtonSlot::MouseLeft, JoyButtonSlot::JoyMouseMovement, this); + bwdButtonSlot = new JoyButtonSlot(JoyButtonSlot::MouseRight, JoyButtonSlot::JoyMouseMovement, this); + QMetaObject::invokeMethod(m_sensor, "setDeadZone", Q_ARG(double, 0)); + QMetaObject::invokeMethod(m_sensor, "setDiagonalRange", Q_ARG(double, 90)); + break; + + case PRESET_MOUSE_INV_HV: + leftButtonSlot = new JoyButtonSlot(JoyButtonSlot::MouseRight, JoyButtonSlot::JoyMouseMovement, this); + rightButtonSlot = new JoyButtonSlot(JoyButtonSlot::MouseLeft, JoyButtonSlot::JoyMouseMovement, this); + upButtonSlot = new JoyButtonSlot(JoyButtonSlot::MouseDown, JoyButtonSlot::JoyMouseMovement, this); + downButtonSlot = new JoyButtonSlot(JoyButtonSlot::MouseUp, JoyButtonSlot::JoyMouseMovement, this); + fwdButtonSlot = new JoyButtonSlot(JoyButtonSlot::MouseRight, JoyButtonSlot::JoyMouseMovement, this); + bwdButtonSlot = new JoyButtonSlot(JoyButtonSlot::MouseLeft, JoyButtonSlot::JoyMouseMovement, this); + QMetaObject::invokeMethod(m_sensor, "setDeadZone", Q_ARG(double, 0)); + QMetaObject::invokeMethod(m_sensor, "setDiagonalRange", Q_ARG(double, 90)); + break; + + case PRESET_ARROWS: + leftButtonSlot = new JoyButtonSlot(AntKeyMapper::getInstance()->returnVirtualKey(Qt::Key_Left), Qt::Key_Left, + JoyButtonSlot::JoyKeyboard, this); + rightButtonSlot = new JoyButtonSlot(AntKeyMapper::getInstance()->returnVirtualKey(Qt::Key_Right), Qt::Key_Right, + JoyButtonSlot::JoyKeyboard, this); + upButtonSlot = new JoyButtonSlot(AntKeyMapper::getInstance()->returnVirtualKey(Qt::Key_Up), Qt::Key_Up, + JoyButtonSlot::JoyKeyboard, this); + downButtonSlot = new JoyButtonSlot(AntKeyMapper::getInstance()->returnVirtualKey(Qt::Key_Down), Qt::Key_Down, + JoyButtonSlot::JoyKeyboard, this); + QMetaObject::invokeMethod(m_sensor, "setDeadZone", Q_ARG(double, 15)); + QMetaObject::invokeMethod(m_sensor, "setDiagonalRange", Q_ARG(double, 45)); + break; + + case PRESET_WASD: + leftButtonSlot = new JoyButtonSlot(AntKeyMapper::getInstance()->returnVirtualKey(Qt::Key_A), Qt::Key_A, + JoyButtonSlot::JoyKeyboard, this); + rightButtonSlot = new JoyButtonSlot(AntKeyMapper::getInstance()->returnVirtualKey(Qt::Key_D), Qt::Key_D, + JoyButtonSlot::JoyKeyboard, this); + upButtonSlot = new JoyButtonSlot(AntKeyMapper::getInstance()->returnVirtualKey(Qt::Key_W), Qt::Key_W, + JoyButtonSlot::JoyKeyboard, this); + downButtonSlot = new JoyButtonSlot(AntKeyMapper::getInstance()->returnVirtualKey(Qt::Key_S), Qt::Key_S, + JoyButtonSlot::JoyKeyboard, this); + QMetaObject::invokeMethod(m_sensor, "setDeadZone", Q_ARG(double, 15)); + QMetaObject::invokeMethod(m_sensor, "setDiagonalRange", Q_ARG(double, 45)); + break; + + case PRESET_NUMPAD: + leftButtonSlot = new JoyButtonSlot(AntKeyMapper::getInstance()->returnVirtualKey(QtKeyMapperBase::AntKey_KP_4), + QtKeyMapperBase::AntKey_KP_4, JoyButtonSlot::JoyKeyboard, this); + rightButtonSlot = new JoyButtonSlot(AntKeyMapper::getInstance()->returnVirtualKey(QtKeyMapperBase::AntKey_KP_6), + QtKeyMapperBase::AntKey_KP_6, JoyButtonSlot::JoyKeyboard, this); + upButtonSlot = new JoyButtonSlot(AntKeyMapper::getInstance()->returnVirtualKey(QtKeyMapperBase::AntKey_KP_8), + QtKeyMapperBase::AntKey_KP_8, JoyButtonSlot::JoyKeyboard, this); + downButtonSlot = new JoyButtonSlot(AntKeyMapper::getInstance()->returnVirtualKey(QtKeyMapperBase::AntKey_KP_2), + QtKeyMapperBase::AntKey_KP_2, JoyButtonSlot::JoyKeyboard, this); + QMetaObject::invokeMethod(m_sensor, "setDeadZone", Q_ARG(double, 15)); + QMetaObject::invokeMethod(m_sensor, "setDiagonalRange", Q_ARG(double, 45)); + break; + } + + QHash tempHash; + if (upButtonSlot != nullptr) + tempHash.insert(SENSOR_UP, upButtonSlot); + if (downButtonSlot != nullptr) + tempHash.insert(SENSOR_DOWN, downButtonSlot); + if (leftButtonSlot != nullptr) + tempHash.insert(SENSOR_LEFT, leftButtonSlot); + if (rightButtonSlot != nullptr) + tempHash.insert(SENSOR_RIGHT, rightButtonSlot); + if (fwdButtonSlot != nullptr) + tempHash.insert(SENSOR_FWD, fwdButtonSlot); + if (bwdButtonSlot != nullptr) + tempHash.insert(SENSOR_BWD, bwdButtonSlot); + + m_helper.setPendingSlots(&tempHash); + QMetaObject::invokeMethod(&m_helper, "setFromPendingSlots", Qt::BlockingQueuedConnection); +} + +/** + * @brief Get the internal JoySensorIoThreadHelper + */ +JoySensorIoThreadHelper &JoySensorPreset::getHelper() { return m_helper; } diff --git a/src/joysensorpreset.h b/src/joysensorpreset.h new file mode 100644 index 000000000..79c86d72d --- /dev/null +++ b/src/joysensorpreset.h @@ -0,0 +1,52 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include "uihelpers/joysensoriothreadhelper.h" + +/** + * @brief Defines presets for a sensor + */ +class JoySensorPreset : public QObject +{ + Q_OBJECT + + public: + enum Preset + { + PRESET_NONE, + PRESET_MOUSE, + PRESET_MOUSE_INV_H, + PRESET_MOUSE_INV_V, + PRESET_MOUSE_INV_HV, + PRESET_ARROWS, + PRESET_WASD, + PRESET_NUMPAD + }; + + explicit JoySensorPreset(JoySensor *sensor, QObject *parent = nullptr); + + QList getAvailablePresets(); + Preset currentPreset(); + QString getPresetName(Preset); + void setSensorPreset(Preset); + JoySensorIoThreadHelper &getHelper(); + + private: + JoySensor *m_sensor; + JoySensorIoThreadHelper m_helper; +}; diff --git a/src/joysensorpushbutton.cpp b/src/joysensorpushbutton.cpp new file mode 100644 index 000000000..77e1721f2 --- /dev/null +++ b/src/joysensorpushbutton.cpp @@ -0,0 +1,100 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "joysensorpushbutton.h" + +#include "joysensor.h" +#include "joysensorcontextmenu.h" + +#include + +JoySensorPushButton::JoySensorPushButton(JoySensor *sensor, bool displayNames, QWidget *parent) + : FlashButtonWidget(displayNames, parent) + , m_sensor(sensor) +{ + refreshLabel(); + + tryFlash(); + + setContextMenuPolicy(Qt::CustomContextMenu); + connect(this, &JoySensorPushButton::customContextMenuRequested, this, &JoySensorPushButton::showContextMenu); + + connect(m_sensor, &JoySensor::active, this, &JoySensorPushButton::flash, Qt::QueuedConnection); + connect(m_sensor, &JoySensor::released, this, &JoySensorPushButton::unflash, Qt::QueuedConnection); + connect(m_sensor, &JoySensor::sensorNameChanged, this, &JoySensorPushButton::refreshLabel); +} + +/** + * @brief Get the underlying JoySensor object. + */ +JoySensor *JoySensorPushButton::getSensor() const { return m_sensor; } + +/** + * @brief Generate the string that will be displayed on the button + * @return Display string + */ +QString JoySensorPushButton::generateLabel() +{ + QString temp = QString(); + if (!m_sensor->getSensorName().isEmpty() && ifDisplayNames()) + temp.append(m_sensor->getPartialName(false, true)); + else + temp.append(m_sensor->getPartialName(false)); + + qDebug() << "Name of joy sensor push button: " << temp; + + return temp; +} + +/** + * @brief Disables highlight when the sensor axis is moved + */ +void JoySensorPushButton::disableFlashes() +{ + disconnect(m_sensor, &JoySensor::active, this, &JoySensorPushButton::flash); + disconnect(m_sensor, &JoySensor::released, this, &JoySensorPushButton::unflash); + unflash(); +} + +/** + * @brief Enables highlight when the sensor axis is moved + */ +void JoySensorPushButton::enableFlashes() +{ + connect(m_sensor, &JoySensor::active, this, &JoySensorPushButton::flash, Qt::QueuedConnection); + connect(m_sensor, &JoySensor::released, this, &JoySensorPushButton::unflash, Qt::QueuedConnection); +} + +/** + * @brief Shows sensor context menu + */ +void JoySensorPushButton::showContextMenu(const QPoint &point) +{ + QPoint globalPos = mapToGlobal(point); + JoySensorContextMenu *contextMenu = new JoySensorContextMenu(m_sensor, this); + contextMenu->buildMenu(); + contextMenu->popup(globalPos); +} + +/** + * @brief Highlights the button when sensor is not centered + */ +void JoySensorPushButton::tryFlash() +{ + if (m_sensor->getCurrentDirection() != JoySensorDirection::SENSOR_CENTERED) + flash(); +} diff --git a/src/joysensorpushbutton.h b/src/joysensorpushbutton.h new file mode 100644 index 000000000..28a62255e --- /dev/null +++ b/src/joysensorpushbutton.h @@ -0,0 +1,49 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include "flashbuttonwidget.h" + +class JoySensor; +class QWidget; + +/** + * @brief The central button in a SensorPushButtonGroup + */ +class JoySensorPushButton : public FlashButtonWidget +{ + Q_OBJECT + + public: + explicit JoySensorPushButton(JoySensor *sensor, bool displayNames, QWidget *parent = nullptr); + + JoySensor *getSensor() const; + void tryFlash(); + + protected: + virtual QString generateLabel() override; + + public slots: + void disableFlashes() override; + void enableFlashes() override; + + private slots: + void showContextMenu(const QPoint &point); + + private: + JoySensor *m_sensor; +}; diff --git a/src/joysensorstatusbox.cpp b/src/joysensorstatusbox.cpp new file mode 100644 index 000000000..5a9351fef --- /dev/null +++ b/src/joysensorstatusbox.cpp @@ -0,0 +1,254 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "joysensorstatusbox.h" + +#include "common.h" +#include "globalvariables.h" +#include "joyaxis.h" +#include "joysensor.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +JoySensorStatusBox::JoySensorStatusBox(QWidget *parent) + : QWidget(parent) + , m_sensor(nullptr) +{ +} + +/** + * @brief Sets the to be visualized sensor. + * + * It disconnects all events to the previous sensor if there was any, and + * connects them to the new sensor. + */ +void JoySensorStatusBox::setSensor(JoySensor *sensor) +{ + if (m_sensor != nullptr) + { + disconnect(m_sensor, SIGNAL(deadZoneChanged(double)), this, nullptr); + disconnect(m_sensor, SIGNAL(moved(float, float, float)), this, nullptr); + disconnect(m_sensor, SIGNAL(diagonalRangeChanged(double)), this, nullptr); + disconnect(m_sensor, SIGNAL(maxZoneChanged(double)), this, nullptr); + } + + m_sensor = sensor; + connect(m_sensor, SIGNAL(deadZoneChanged(double)), this, SLOT(update())); + connect(m_sensor, SIGNAL(moved(float, float, float)), this, SLOT(update())); + connect(m_sensor, SIGNAL(diagonalRangeChanged(double)), this, SLOT(update())); + connect(m_sensor, SIGNAL(maxZoneChanged(double)), this, SLOT(update())); + + update(); +} + +/** + * @brief Get the visualized sensor object + */ +JoySensor *JoySensorStatusBox::getSensor() const { return m_sensor; } + +QSize JoySensorStatusBox::sizeHint() const { return QSize(-1, -1); } + +void JoySensorStatusBox::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + + PadderCommon::inputDaemonMutex.lock(); + drawArtificialHorizon(); + PadderCommon::inputDaemonMutex.unlock(); +} + +/** + * @brief draws the artificial horizon instrument on the screen + */ +void JoySensorStatusBox::drawArtificialHorizon() +{ + QPainter paint(this); + paint.setRenderHint(QPainter::Antialiasing, true); + + int side = qMin(width(), height()); + + QPen pen; + QPixmap pix(side, side); + pix.fill(Qt::transparent); + QPainter painter(&pix); + painter.setRenderHint(QPainter::Antialiasing, true); + + // Switch to centric coordinate system + painter.translate(side / 2.0, side / 2.0); + painter.scale(side * 0.45, -side * 0.45); + painter.save(); + + // Draw moving instrument parts + QPainterPath clippingPath; + clippingPath.addEllipse(QPointF(0, 0), 1, 1); + painter.setClipPath(clippingPath); + JoySensorType type; + + double pitch, roll, yaw; + if (m_sensor != nullptr) + { + type = m_sensor->getType(); + if (type == ACCELEROMETER) + { + pitch = -JoySensor::radToDeg(m_sensor->calculatePitch()); + roll = JoySensor::radToDeg(m_sensor->calculateRoll()); + yaw = 0; + } else + { + pitch = -m_sensor->getXCoordinate(); + roll = m_sensor->getYCoordinate(); + yaw = -m_sensor->getZCoordinate(); + } + } else + { + type = ACCELEROMETER; + pitch = 0; + roll = 0; + yaw = 0; + } + + pitch = qBound(-180.0, pitch, 180.0); + roll = qBound(-180.0, roll, 180.0); + yaw = qBound(-180.0, yaw, 180.0); + painter.translate(yaw / 90, pitch / 90); + painter.rotate(roll); + + pen.setColor(Qt::transparent); + painter.setPen(pen); + painter.setBrush(QBrush(QColor(64, 128, 255))); + painter.drawRect(QRectF(-10, 0, 20, 10)); + painter.setBrush(QBrush(Qt::black)); + painter.drawRect(QRectF(-10, -10, 20, 10)); + + // Draw dead zone + pen.setColor(Qt::red); + pen.setWidthF(0.02); + painter.setPen(pen); + painter.setBrush(QBrush(QColor(255, 0, 0, 128))); + double deadZone = m_sensor != nullptr ? m_sensor->getDeadZone() : 0.0; + painter.drawEllipse(QPointF(0, 0), deadZone / 90, deadZone / 90); + + // Draw max zone + QPainterPath maxZonePath; + double maxZone = m_sensor != nullptr ? m_sensor->getMaxZone() : 0.0; + maxZonePath.addEllipse(QPointF(0, 0), 10, 10); + maxZonePath.addEllipse(QPointF(0, 0), maxZone / 90, maxZone / 90); + pen.setColor(Qt::darkGreen); + pen.setWidthF(0.02); + painter.setPen(pen); + painter.setBrush(QBrush(QColor(0, 128, 0, 128))); + painter.drawPath(maxZonePath); + + // Draw diagonal zones + pen.setColor(Qt::green); + painter.setPen(pen); + painter.setBrush(QBrush(QColor(0, 255, 0, 128))); + + double diagonalRange = m_sensor != nullptr ? m_sensor->getDiagonalRange() : 0.0; + if (type == GYROSCOPE) + { + for (int i = 0; i < 4; ++i) + { + painter.drawPie(QRectF(-maxZone / 90, -maxZone / 90, 2 * maxZone / 90, 2 * maxZone / 90), + (45 + 90 * i - diagonalRange / 2) * 16, diagonalRange * 16); + } + } else + { + painter.drawPie(QRectF(-maxZone / 90, -maxZone / 90, 2 * maxZone / 90, 2 * maxZone / 90), + (135 - diagonalRange / 2) * 16, (diagonalRange + 90) * 16); + painter.drawPie(QRectF(-maxZone / 90, -maxZone / 90, 2 * maxZone / 90, 2 * maxZone / 90), + (-45 - diagonalRange / 2) * 16, (diagonalRange + 90) * 16); + } + + // Pitch scale: 30deg per line + pen.setColor(Qt::white); + pen.setWidthF(0.025); + painter.setPen(pen); + painter.setBrush(QBrush(Qt::transparent)); + for (int j = -180; j <= 180; j += 30) + { + painter.drawLine(QPointF(-10, j / 90.0), QPointF(10, j / 90.0)); + } + + // Yaw scale: 30deg per line + if (type == GYROSCOPE) + { + pen.setColor(Qt::white); + pen.setWidthF(0.025); + painter.setPen(pen); + painter.setBrush(QBrush(Qt::transparent)); + for (int j = -180; j <= 180; j += 30) + { + painter.drawLine(QPointF(j / 90.0, -10), QPointF(j / 90.0, 10)); + } + } + + // Draw fixed instrument parts + painter.restore(); + painter.save(); + pen.setColor(QColor(80, 80, 80)); + pen.setWidthF(0.2); + painter.setPen(pen); + painter.setBrush(Qt::NoBrush); + painter.drawEllipse(QPointF(0, 0), 1, 1); + + // Draw scale + pen.setWidthF(0.05); + pen.setColor(Qt::yellow); + painter.setPen(pen); + + painter.drawLine(QPointF(0.3, 0), QPointF(0.2, 0)); + painter.drawLine(QPointF(-0.3, 0), QPointF(-0.2, 0)); + painter.drawArc(QRectF(-0.2, -0.2, 0.4, 0.4), 0 * 16, 180 * 16); + painter.drawPoint(QPointF(0, 0)); + + pen.setColor(Qt::white); + painter.setPen(pen); + for (int j = 0; j < 19; ++j) + { + painter.drawLine(QPointF(1, 0), QPointF(0.9, 0)); + painter.rotate(10.0); + } + + // Draw dead zone + painter.restore(); + pen.setColor(Qt::red); + pen.setWidthF(0.1); + painter.setPen(pen); + painter.setOpacity(0.5); + painter.drawArc(QRectF(-1, -1, 2, 2), -16 * deadZone, 16 * deadZone * 2); + painter.drawArc(QRectF(-1, -1, 2, 2), 16 * (180 - deadZone), 16 * deadZone * 2); + + // Draw max zone + pen.setColor(Qt::darkGreen); + painter.setPen(pen); + double tmpMaxZone = std::min(maxZone, 90.0); + painter.drawArc(QRectF(-1, -1, 2, 2), 16 * (90 - (90 - tmpMaxZone)), 16 * (90 - tmpMaxZone) * 2); + painter.drawArc(QRectF(-1, -1, 2, 2), 16 * (270 - (90 - tmpMaxZone)), 16 * (90 - tmpMaxZone) * 2); + + // Draw to window + paint.setCompositionMode(QPainter::CompositionMode_SourceOver); + paint.drawPixmap(pix.rect(), pix); +} diff --git a/src/joysensorstatusbox.h b/src/joysensorstatusbox.h new file mode 100644 index 000000000..7502d07f4 --- /dev/null +++ b/src/joysensorstatusbox.h @@ -0,0 +1,50 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include +#include + +class JoySensor; +class QPaintEvent; + +/** + * @brief The GUI sensor position indicator widget + * + * It shows the yaw and roll angles on an artificial horizon instrument. + * In case of a gyroscope, the inner parts has an additional horizontal + * degree of freedom. + */ +class JoySensorStatusBox : public QWidget +{ + Q_OBJECT + + public: + explicit JoySensorStatusBox(QWidget *parent = nullptr); + + void setSensor(JoySensor *sensor); + JoySensor *getSensor() const; + + virtual QSize sizeHint() const override; + + protected: + virtual void paintEvent(QPaintEvent *event) override; + void drawArtificialHorizon(); + + private: + JoySensor *m_sensor; +}; diff --git a/src/joysensortype.h b/src/joysensortype.h new file mode 100644 index 000000000..c499a0468 --- /dev/null +++ b/src/joysensortype.h @@ -0,0 +1,34 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include + +enum JoySensorType +{ + ACCELEROMETER, + GYROSCOPE, +#if SDL_VERSION_ATLEAST(2, 0, 14) + SENSOR_COUNT +#else + SENSOR_COUNT = 0 +#endif +}; + +Q_DECLARE_METATYPE(JoySensorType) diff --git a/src/joystick.cpp b/src/joystick.cpp index 94d78d97f..26b709d58 100644 --- a/src/joystick.cpp +++ b/src/joystick.cpp @@ -149,6 +149,10 @@ int Joystick::getNumberRawHats() return numhats; } +double Joystick::getRawSensorRate(JoySensorType _) { return 0; } + +bool Joystick::hasRawSensor(JoySensorType _) { return false; } + void Joystick::setCounterUniques(int counter) { counterUniques = counter; } SDL_JoystickID Joystick::getSDLJoystickID() { return joystickID; } diff --git a/src/joystick.h b/src/joystick.h index 89ce84dd2..31bc4a337 100644 --- a/src/joystick.h +++ b/src/joystick.h @@ -46,6 +46,9 @@ class Joystick : public InputDevice virtual int getNumberRawButtons() override; virtual int getNumberRawAxes() override; virtual int getNumberRawHats() override; + virtual double getRawSensorRate(JoySensorType type) override; + virtual bool hasRawSensor(JoySensorType type) override; + void setCounterUniques(int counter) override; SDL_Joystick *getJoyhandle() const; diff --git a/src/main.cpp b/src/main.cpp index 127d7fbd8..b7c148959 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -25,6 +25,8 @@ #include "inputdaemon.h" #include "inputdevice.h" #include "joybuttonslot.h" +#include "joysensordirection.h" +#include "joysensortype.h" #include "localantimicroserver.h" #include "mainwindow.h" #include "setjoystick.h" @@ -237,6 +239,8 @@ int main(int argc, char *argv[]) qRegisterMetaType(); qRegisterMetaType("SDL_JoystickID"); qRegisterMetaType("JoyButtonSlot::JoySlotInputAction"); + qRegisterMetaType(); + qRegisterMetaType(); #if defined(WITH_X11) diff --git a/src/mousedialog/mousesensorsettingsdialog.cpp b/src/mousedialog/mousesensorsettingsdialog.cpp new file mode 100644 index 000000000..7c2509a85 --- /dev/null +++ b/src/mousedialog/mousesensorsettingsdialog.cpp @@ -0,0 +1,201 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "mousesensorsettingsdialog.h" + +#include "common.h" +#include "inputdevice.h" +#include "joybuttontypes/joysensorbutton.h" +#include "joysensor.h" +#include "setjoystick.h" + +#include +#include +#include +#include + +MouseSensorSettingsDialog::MouseSensorSettingsDialog(JoySensor *sensor, QWidget *parent) + : MouseSettingsDialog(parent) + , m_sensor(sensor) +{ + setAttribute(Qt::WA_DeleteOnClose); + + ui->topGroupBox->setVisible(false); + ui->springGroupBox->setVisible(false); + ui->extraAccelerationGroupBox->setVisible(false); + ui->sensLabel->setVisible(false); + ui->sensitivityDoubleSpinBox->setVisible(false); + ui->easingDurationLabel->setVisible(false); + ui->easingDoubleSpinBox->setVisible(false); + + calculateMouseSpeedPreset(); + + updateWindowTitleSensorName(); + calculateWheelSpeedPreset(); + + changeSettingsWidgetStatus(ui->accelerationComboBox->currentIndex()); + + connect(ui->horizontalSpinBox, static_cast(&QSpinBox::valueChanged), this, + &MouseSensorSettingsDialog::updateConfigHorizontalSpeed); + connect(ui->verticalSpinBox, static_cast(&QSpinBox::valueChanged), this, + &MouseSensorSettingsDialog::updateConfigVerticalSpeed); + + connect(ui->wheelHoriSpeedSpinBox, static_cast(&QSpinBox::valueChanged), this, + &MouseSensorSettingsDialog::updateWheelSpeedHorizontalSpeed); + connect(ui->wheelVertSpeedSpinBox, static_cast(&QSpinBox::valueChanged), this, + &MouseSensorSettingsDialog::updateWheelSpeedVerticalSpeed); +} + +/** + * @brief Gets the underlying JoySensor object + */ +JoySensor *MouseSensorSettingsDialog::getSensor() const { return m_sensor; } + +/** + * @brief Get the maximum mouse speed used by the buttons of the associated + * sensor and set the UI values to it. + */ +void MouseSensorSettingsDialog::calculateMouseSpeedPreset() +{ + auto buttons = m_sensor->getButtons(); + int mouseSpeedX = 0; + int mouseSpeedY = 0; + for (auto iter = buttons->cbegin(); iter != buttons->cend(); ++iter) + { + JoySensorButton *button = iter.value(); + mouseSpeedX = qMax(mouseSpeedX, button->getMouseSpeedX()); + mouseSpeedY = qMax(mouseSpeedY, button->getMouseSpeedY()); + } + + ui->horizontalSpinBox->setValue(mouseSpeedX); + ui->verticalSpinBox->setValue(mouseSpeedY); +} + +/** + * @brief Get the maximum mouse wheel speed used by the buttons of the associated + * sensor and set the UI values to it. + */ +void MouseSensorSettingsDialog::calculateWheelSpeedPreset() +{ + auto buttons = m_sensor->getButtons(); + int wheelSpeedX = 0; + int wheelSpeedY = 0; + for (auto iter = buttons->cbegin(); iter != buttons->cend(); ++iter) + { + JoySensorButton *button = iter.value(); + wheelSpeedX = qMax(wheelSpeedX, button->getWheelSpeedX()); + wheelSpeedY = qMax(wheelSpeedY, button->getWheelSpeedY()); + } + + ui->wheelHoriSpeedSpinBox->setValue(wheelSpeedX); + ui->wheelVertSpeedSpinBox->setValue(wheelSpeedY); +} + +/** + * @brief Shows the sensor name in dialog title + */ +void MouseSensorSettingsDialog::updateWindowTitleSensorName() +{ + QString temp = QString(tr("Mouse Settings")).append(" - "); + + if (!m_sensor->getSensorName().isEmpty()) + temp.append(m_sensor->getPartialName(false, true)); + else + temp.append(m_sensor->getPartialName()); + + if (m_sensor->getParentSet()->getIndex() != 0) + { + int setIndex = m_sensor->getParentSet()->getRealIndex(); + temp.append(" [").append(tr("Set %1").arg(setIndex)); + + QString setName = m_sensor->getParentSet()->getName(); + if (!setName.isEmpty()) + temp.append(": ").append(setName); + + temp.append("]"); + } + + setWindowTitle(temp); +} + +/** + * @brief Horizontal mouse speed change UI event handler + * Updates horizontal mouse speed on all buttons of the associated sensor. + */ +void MouseSensorSettingsDialog::updateConfigHorizontalSpeed(int value) +{ + auto buttons = m_sensor->getButtons(); + for (auto iter = buttons->begin(); iter != buttons->end(); ++iter) + { + JoySensorButton *button = iter.value(); + button->setMouseSpeedX(value); + } +} + +/** + * @brief Vertical mouse speed change UI event handler + * Updates vertical mouse speed on all buttons of the associated sensor. + */ +void MouseSensorSettingsDialog::updateConfigVerticalSpeed(int value) +{ + auto buttons = m_sensor->getButtons(); + for (auto iter = buttons->begin(); iter != buttons->end(); ++iter) + { + JoySensorButton *button = iter.value(); + button->setMouseSpeedY(value); + } +} + +/** + * @brief Horizontal mouse wheel speed change UI event handler + * Updates horizontal mouse wheel speed on all buttons of the associated sensor. + */ +void MouseSensorSettingsDialog::updateWheelSpeedHorizontalSpeed(int value) +{ + auto buttons = m_sensor->getButtons(); + for (auto iter = buttons->begin(); iter != buttons->end(); ++iter) + { + JoySensorButton *button = iter.value(); + button->setWheelSpeed(value, 'X'); + } +} + +/** + * @brief Vertical mouse wheel speed change UI event handler + * Updates vertical mouse wheel speed on all buttons of the associated sensor. + */ +void MouseSensorSettingsDialog::updateWheelSpeedVerticalSpeed(int value) +{ + auto buttons = m_sensor->getButtons(); + for (auto iter = buttons->begin(); iter != buttons->end(); ++iter) + { + JoySensorButton *button = iter.value(); + button->setWheelSpeed(value, 'Y'); + } +} + +/** + * @brief Not used for sensors but necessary to implement because it is an + * abstract function in the parent class. + */ +void MouseSensorSettingsDialog::changeMouseMode(int index) { Q_UNUSED(index); } + +/** + * @brief Not used for sensors but necessary to implement because it is an + * abstract function in the parent class. + */ +void MouseSensorSettingsDialog::changeMouseCurve(int index) { Q_UNUSED(index); } diff --git a/src/mousedialog/mousesensorsettingsdialog.h b/src/mousedialog/mousesensorsettingsdialog.h new file mode 100644 index 000000000..9b2a5e35d --- /dev/null +++ b/src/mousedialog/mousesensorsettingsdialog.h @@ -0,0 +1,54 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include "ui_mousesettingsdialog.h" + +#include "mousesettingsdialog.h" + +class JoySensor; +class SpringModeRegionPreview; + +/** + * @brief Mouse settings dialog specialized for sensors + */ +class MouseSensorSettingsDialog : public MouseSettingsDialog +{ + Q_OBJECT + + public: + explicit MouseSensorSettingsDialog(JoySensor *sensor, QWidget *parent = 0); + + JoySensor *getSensor() const; + + protected: + void calculateMouseSpeedPreset(); + void calculateWheelSpeedPreset(); + void updateWindowTitleSensorName(); + + public slots: + void updateConfigHorizontalSpeed(int value); + void updateConfigVerticalSpeed(int value); + void updateWheelSpeedHorizontalSpeed(int value); + void updateWheelSpeedVerticalSpeed(int value); + + virtual void changeMouseMode(int index); + virtual void changeMouseCurve(int index); + + private: + JoySensor *m_sensor; +}; diff --git a/src/sdleventreader.cpp b/src/sdleventreader.cpp index 75e91775d..002957369 100644 --- a/src/sdleventreader.cpp +++ b/src/sdleventreader.cpp @@ -59,9 +59,17 @@ SDLEventReader::~SDLEventReader() } void SDLEventReader::initSDL() -{ // SDL_INIT_GAMECONTROLLER should automatically initialize SDL_INIT_JOYSTICK +{ + // SDL_INIT_GAMECONTROLLER should automatically initialize SDL_INIT_JOYSTICK // but it doesn't seem to be the case with v2.0.4 + // Passing SDL_INIT_SENSOR here triggers bug libsdl-org/SDL#4276 on windows + // with v2.0.20. However, sensors works without in Linux and Windows so + // skip it. + //#if SDL_VERSION_ATLEAST(2, 0, 14) + // SDL_Init(SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK | SDL_INIT_SENSOR); + //#else SDL_Init(SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK); + //#endif SDL_JoystickEventState(SDL_ENABLE); sdlIsOpen = true; diff --git a/src/sensorpushbuttongroup.cpp b/src/sensorpushbuttongroup.cpp new file mode 100644 index 000000000..5d16e1bfc --- /dev/null +++ b/src/sensorpushbuttongroup.cpp @@ -0,0 +1,118 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "sensorpushbuttongroup.h" + +#include "buttoneditdialog.h" +#include "inputdevice.h" +#include "joybuttontypes/joysensorbutton.h" +#include "joysensor.h" +#include "joysensorbuttonpushbutton.h" +#include "joysensoreditdialog.h" +#include "joysensorpushbutton.h" + +#include +#include +#include + +SensorPushButtonGroup::SensorPushButtonGroup(JoySensor *sensor, bool keypadUnlocked, bool displayNames, QWidget *parent) + : QGridLayout(parent) + , m_sensor(sensor) + , m_display_names(displayNames) + , m_keypad_unlocked(keypadUnlocked) +{ + m_left_button = generateBtnToGrid(SENSOR_LEFT, 1, 0); + m_right_button = generateBtnToGrid(SENSOR_RIGHT, 1, 2); + m_up_button = generateBtnToGrid(SENSOR_UP, 0, 1); + m_down_button = generateBtnToGrid(SENSOR_DOWN, 2, 1); + m_bwd_button = generateBtnToGrid(SENSOR_BWD, 0, 2); + + if (m_sensor->getType() == GYROSCOPE) + m_fwd_button = generateBtnToGrid(SENSOR_FWD, 2, 0); + else + m_fwd_button = nullptr; + + m_sensor_widget = new JoySensorPushButton(m_sensor, m_display_names, parentWidget()); + m_sensor_widget->setIcon( + QIcon::fromTheme(QString::fromUtf8("games_config_options"), QIcon(":/images/actions/games_config_options.png"))); + + connect(m_sensor_widget, &JoySensorPushButton::clicked, this, &SensorPushButtonGroup::showSensorDialog); + + addWidget(m_sensor_widget, 1, 1); +} + +/** + * @brief Generates a new push button at the given grid coordinates + * @returns Newly created push button + */ +JoySensorButtonPushButton *SensorPushButtonGroup::generateBtnToGrid(JoySensorDirection sensorDir, int gridRow, int gridCol) +{ + JoySensorButton *button = m_sensor->getButtons()->value(sensorDir); + JoySensorButtonPushButton *pushbutton = new JoySensorButtonPushButton(button, m_display_names, parentWidget()); + + connect(pushbutton, &JoySensorButtonPushButton::clicked, this, + [this, pushbutton] { openSensorButtonDialog(pushbutton); }); + + button->establishPropertyUpdatedConnections(); + connect(button, &JoySensorButton::slotsChanged, this, &SensorPushButtonGroup::propagateSlotsChanged); + + addWidget(pushbutton, gridRow, gridCol); + return pushbutton; +} + +void SensorPushButtonGroup::propagateSlotsChanged() { emit buttonSlotChanged(); } + +/** + * @brief Get the underlying JoySensor object. + */ +JoySensor *SensorPushButtonGroup::getSensor() const { return m_sensor; } + +/** + * @brief Shows the button mapping dialog for the given direction button + */ +void SensorPushButtonGroup::openSensorButtonDialog(JoySensorButtonPushButton *pushbutton) +{ + ButtonEditDialog *dialog = new ButtonEditDialog(pushbutton->getButton(), m_sensor->getParentSet()->getInputDevice(), + m_keypad_unlocked, parentWidget()); + dialog->show(); +} + +/** + * @brief Shows the sensor settings dialog + */ +void SensorPushButtonGroup::showSensorDialog() +{ + JoySensorEditDialog *dialog = new JoySensorEditDialog(m_sensor, parentWidget()); + dialog->show(); +} + +void SensorPushButtonGroup::toggleNameDisplay() +{ + m_display_names = !m_display_names; + + m_up_button->toggleNameDisplay(); + m_down_button->toggleNameDisplay(); + m_left_button->toggleNameDisplay(); + m_right_button->toggleNameDisplay(); + m_bwd_button->toggleNameDisplay(); + if (m_fwd_button != nullptr) + m_fwd_button->toggleNameDisplay(); + + m_sensor_widget->toggleNameDisplay(); +} + +bool SensorPushButtonGroup::ifDisplayNames() const { return m_display_names; } diff --git a/src/sensorpushbuttongroup.h b/src/sensorpushbuttongroup.h new file mode 100644 index 000000000..ca7afe909 --- /dev/null +++ b/src/sensorpushbuttongroup.h @@ -0,0 +1,70 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include "joysensordirection.h" + +#include + +class JoySensor; +class QWidget; +class JoySensorButtonPushButton; +class JoySensorPushButton; + +/** + * @brief The sensor button mapping widget in the main window. + * The layout is based on a isometric 3D view with the regular + * XY axes and a diagonal Z axis. + */ +class SensorPushButtonGroup : public QGridLayout +{ + Q_OBJECT + + public: + explicit SensorPushButtonGroup(JoySensor *sensor, bool keypadUnlocked, bool displayNames = false, + QWidget *parent = nullptr); + JoySensor *getSensor() const; + + bool ifDisplayNames() const; + + signals: + void buttonSlotChanged(); + + public slots: + void toggleNameDisplay(); + + private slots: + void propagateSlotsChanged(); + void openSensorButtonDialog(JoySensorButtonPushButton *pushbutton); + void showSensorDialog(); + + private: + JoySensor *m_sensor; + bool m_display_names; + bool m_keypad_unlocked; + + JoySensorButtonPushButton *m_up_button; + JoySensorButtonPushButton *m_down_button; + JoySensorButtonPushButton *m_left_button; + JoySensorButtonPushButton *m_right_button; + JoySensorButtonPushButton *m_fwd_button; + JoySensorButtonPushButton *m_bwd_button; + + JoySensorPushButton *m_sensor_widget; + + JoySensorButtonPushButton *generateBtnToGrid(JoySensorDirection sensorDir, int gridRow, int gridCol); +}; diff --git a/src/setjoystick.cpp b/src/setjoystick.cpp index 7bb95c7b2..f04b1a83f 100644 --- a/src/setjoystick.cpp +++ b/src/setjoystick.cpp @@ -22,8 +22,11 @@ #include "inputdevice.h" #include "joybutton.h" #include "joybuttontypes/joycontrolstickbutton.h" +#include "joybuttontypes/joysensorbutton.h" #include "joycontrolstick.h" #include "joydpad.h" +#include "joysensor.h" +#include "joysensorfactory.h" #include "vdpad.h" #include @@ -65,6 +68,8 @@ VDPad *SetJoystick::getVDPad(int index) const { return getVdpads().value(index); JoyControlStick *SetJoystick::getJoyStick(int index) const { return getSticks().value(index); } +JoySensor *SetJoystick::getSensor(JoySensorType type) const { return m_sensors.value(type); } + void SetJoystick::refreshButtons() { deleteButtons(); @@ -109,6 +114,26 @@ void SetJoystick::refreshHats() } } +/** + * @brief Setup sensor objects for all available hardware sensors. + */ +void SetJoystick::refreshSensors() +{ + deleteSensors(); + + for (size_t i = 0; i < SENSOR_COUNT; ++i) + { + JoySensorType type = static_cast(i); + + if (!getInputDevice()->hasRawSensor(type)) + continue; + + JoySensor *sensor = JoySensorFactory::build(type, getInputDevice()->getRawSensorRate(type), m_index, this, this); + m_sensors.insert(type, sensor); + enableSensorConnections(sensor); + } +} + void SetJoystick::deleteButtons() { QHashIterator iter(getButtons()); @@ -199,6 +224,20 @@ void SetJoystick::deleteHats() hats.clear(); } +/** + * @brief Destroy all sensor objects in this set + */ +void SetJoystick::deleteSensors() +{ + for (const auto &sensor : m_sensors) + { + if (sensor != nullptr) + sensor->deleteLater(); + } + + m_sensors.clear(); +} + int SetJoystick::getNumberButtons() const { return getButtons().count(); } int SetJoystick::getNumberAxes() const { return axes.count(); } @@ -207,13 +246,25 @@ int SetJoystick::getNumberHats() const { return getHats().count(); } int SetJoystick::getNumberSticks() const { return getSticks().size(); } +/** + * @brief Checks if this set has a sensor + * @returns True if sensor type is present, false otherwise. + */ +bool SetJoystick::hasSensor(JoySensorType type) const { return m_sensors.contains(type); } + int SetJoystick::getNumberVDPads() const { return getVdpads().size(); } +/** + * @brief Re-enumerates inputs from the associated device and + * resets all mappings in this set. + */ void SetJoystick::reset() { deleteSticks(); + deleteSensors(); deleteVDpads(); refreshAxes(); + refreshSensors(); refreshButtons(); refreshHats(); m_name = QString(); @@ -239,6 +290,16 @@ void SetJoystick::propogateSetStickButtonAssociation(int button, int stick, int emit setAssignmentStickChanged(button, stick, m_index, newset, mode); } +/** + * @brief Forwards set change slot mapping event to InputDevice + */ +void SetJoystick::propagateSetSensorButtonAssociation(JoySensorDirection direction, JoySensorType sensor, int newset, + int mode) +{ + if (newset != m_index) + emit setAssignmentSensorChanged(direction, sensor, m_index, newset, mode); +} + void SetJoystick::propogateSetDPadButtonAssociation(int button, int dpad, int newset, int mode) { if (newset != m_index) @@ -277,6 +338,13 @@ void SetJoystick::release() dpad->eventReset(); } + for (auto &sensor : m_sensors) + { + float values[3] = {0}; + sensor->clearPendingEvent(); + sensor->joyEvent(values, true); + } + QHashIterator iterButtons(getButtons()); while (iterButtons.hasNext()) @@ -288,6 +356,10 @@ void SetJoystick::release() } } +/** + * @brief Check if this set has any mapped event. + * @returns True if any event is mapped to a keyboard or mouse event, false otherwise. + */ bool SetJoystick::isSetEmpty() { bool result = true; @@ -331,6 +403,15 @@ bool SetJoystick::isSetEmpty() result = false; } + for (const auto &sensor : m_sensors) + { + if (!result) + break; + + if (!sensor->isDefault()) + result = false; + } + QHashIterator iter5(getVdpads()); while (iter5.hasNext() && result) @@ -525,6 +606,32 @@ void SetJoystick::propogateSetStickButtonRelease(int button) } } +void SetJoystick::propagateSetSensorButtonClick(int button) +{ + JoySensorButton *sensorButton = qobject_cast(sender()); + + if (sensorButton != nullptr) + { + JoySensor *sensor = sensorButton->getSensor(); + + if (sensor && !sensorButton->getIgnoreEventState()) + emit setSensorButtonClick(m_index, sensor->getType(), static_cast(button)); + } +} + +void SetJoystick::propagateSetSensorButtonRelease(int button) +{ + JoySensorButton *sensorButton = qobject_cast(sender()); + + if (sensorButton != nullptr) + { + JoySensor *sensor = sensorButton->getSensor(); + + if (!sensorButton->getIgnoreEventState()) + emit setSensorButtonRelease(m_index, sensor->getType(), static_cast(button)); + } +} + void SetJoystick::propogateSetDPadButtonClick(int button) { JoyDPadButton *dpadButton = qobject_cast(sender()); @@ -579,6 +686,17 @@ void SetJoystick::propogateSetStickButtonNameChange() connect(button, &JoyControlStickButton::buttonNameChanged, this, &SetJoystick::propogateSetStickButtonNameChange); } +/** + * @brief Propagate button rename event to InputDevice + */ +void SetJoystick::propagateSetSensorButtonNameChange() +{ + JoySensorButton *button = qobject_cast(sender()); + disconnect(button, &JoySensorButton::buttonNameChanged, this, &SetJoystick::propagateSetSensorButtonNameChange); + emit setSensorButtonNameChange(button->getSensor()->getType(), static_cast(button->getJoyNumber())); + connect(button, &JoySensorButton::buttonNameChanged, this, &SetJoystick::propagateSetSensorButtonNameChange); +} + void SetJoystick::propogateSetDPadButtonNameChange() { JoyDPadButton *button = qobject_cast(sender()); @@ -611,6 +729,14 @@ void SetJoystick::propogateSetStickNameChange() connect(stick, &JoyControlStick::stickNameChanged, this, &SetJoystick::propogateSetStickNameChange); } +void SetJoystick::propagateSetSensorNameChange() +{ + JoySensor *sensor = qobject_cast(sender()); + disconnect(sensor, &JoySensor::sensorNameChanged, this, &SetJoystick::propagateSetSensorNameChange); + emit setSensorNameChange(sensor->getType()); + connect(sensor, &JoySensor::sensorNameChanged, this, &SetJoystick::propagateSetSensorNameChange); +} + void SetJoystick::propogateSetDPadNameChange() { JoyDPad *dpad = qobject_cast(sender()); @@ -781,6 +907,27 @@ void SetJoystick::enableHatConnections(JoyDPad *dpad) } } +/** + * @brief Establishes connections for event propagation between JoySensor and InputDevice + */ +void SetJoystick::enableSensorConnections(JoySensor *sensor) +{ + connect(sensor, &JoySensor::sensorNameChanged, this, &SetJoystick::propagateSetSensorNameChange); + + auto buttons = sensor->getButtons(); + for (auto iter = buttons->cbegin(); iter != buttons->cend(); ++iter) + { + connect(iter.value(), &JoySensorButton::setChangeActivated, this, &SetJoystick::propogateSetChange); + connect(iter.value(), &JoySensorButton::setAssignmentChanged, this, + &SetJoystick::propagateSetSensorButtonAssociation); + connect(iter.value(), &JoySensorButton::clicked, this, &SetJoystick::propagateSetSensorButtonClick, + Qt::QueuedConnection); + connect(iter.value(), &JoySensorButton::released, this, &SetJoystick::propagateSetSensorButtonRelease, + Qt::QueuedConnection); + connect(iter.value(), &JoySensorButton::buttonNameChanged, this, &SetJoystick::propagateSetSensorButtonNameChange); + } +} + InputDevice *SetJoystick::getInputDevice() const { return m_device; } void SetJoystick::setName(QString name) @@ -824,6 +971,16 @@ void SetJoystick::copyAssignments(SetJoystick *destSet) sourceStick->copyAssignments(destStick); } + for (auto iter = m_sensors.cbegin(); iter != m_sensors.cend(); ++iter) + { + JoySensorType type = iter.key(); + JoySensor *sourceSensor = iter.value(); + JoySensor *destSensor = destSet->getSensor(type); + + if (sourceSensor && destSensor) + sourceSensor->copyAssignments(destSensor); + } + for (int i = 0; i < m_device->getNumberHats(); i++) { JoyDPad *sourceDPad = getHats().value(i); @@ -949,4 +1106,10 @@ QHash const &SetJoystick::getHats() const { return hats; } QHash const &SetJoystick::getSticks() const { return sticks; } +/** + * @brief Get all sensor objects in this set. + * @returns Sensors in this set + */ +QHash const &SetJoystick::getSensors() const { return m_sensors; } + QHash const &SetJoystick::getVdpads() const { return vdpads; } diff --git a/src/setjoystick.h b/src/setjoystick.h index 11c30fc72..58602c69e 100644 --- a/src/setjoystick.h +++ b/src/setjoystick.h @@ -20,14 +20,22 @@ #define SETJOYSTICK_H #include "joyaxis.h" +#include "joysensordirection.h" +#include "joysensortype.h" #include "xml/setjoystickxml.h" class InputDevice; class JoyButton; class JoyDPad; class JoyControlStick; +class JoySensor; class VDPad; +/** + * @brief A set of mapped events which can by switched by a controller event. + * Contains controller input objects like axes or buttons and their mappings, + * and forwards some QT GUI events. + */ class SetJoystick : public SetJoystickXml { Q_OBJECT @@ -41,17 +49,20 @@ class SetJoystick : public SetJoystickXml JoyButton *getJoyButton(int index) const; JoyDPad *getJoyDPad(int index) const; JoyControlStick *getJoyStick(int index) const; + JoySensor *getSensor(JoySensorType type) const; VDPad *getVDPad(int index) const; int getNumberButtons() const; int getNumberAxes() const; int getNumberHats() const; int getNumberSticks() const; + bool hasSensor(JoySensorType type) const; int getNumberVDPads() const; QHash const &getButtons() const; QHash const &getHats() const; QHash const &getSticks() const; + QHash const &getSensors() const; QHash const &getVdpads() const; QHash *getAxes(); @@ -60,6 +71,7 @@ class SetJoystick : public SetJoystickXml virtual void refreshButtons(); // SetButton class virtual void refreshAxes(); // SetAxis class virtual void refreshHats(); // SetHat class + virtual void refreshSensors(); void release(); void addControlStick(int index, JoyControlStick *stick); // SetStick class void removeControlStick(int index); // SetStick class @@ -87,39 +99,48 @@ class SetJoystick : public SetJoystickXml void deleteAxes(); // SetAxis class void deleteHats(); // SetHat class void deleteSticks(); // SetStick class - void deleteVDpads(); // SetVDPad class + void deleteSensors(); + void deleteVDpads(); // SetVDPad class void enableButtonConnections(JoyButton *button); // SetButton class void enableAxisConnections(JoyAxis *axis); // SetAxis class void enableHatConnections(JoyDPad *dpad); // SetHat class + void enableSensorConnections(JoySensor *sensor); signals: void setChangeActivated(int index); void setAssignmentButtonChanged(int button, int originset, int newset, int mode); // SetButton class void setAssignmentAxisChanged(int button, int axis, int originset, int newset, int mode); // SetAxis class void setAssignmentStickChanged(int button, int stick, int originset, int newset, int mode); // SetStick class - void setAssignmentDPadChanged(int button, int dpad, int originset, int newset, int mode); // SetHat class - void setAssignmentVDPadChanged(int button, int dpad, int originset, int newset, int mode); // SetVDPad class - void setAssignmentAxisThrottleChanged(int axis, int originset); // SetAxis class - void setButtonClick(int index, int button); // SetButton class - void setButtonRelease(int index, int button); // SetButton class - void setAxisButtonClick(int setindex, int axis, int button); // SetAxis class - void setAxisButtonRelease(int setindex, int axis, int button); // SetAxis class - void setAxisActivated(int setindex, int axis, int value); // SetAxis class - void setAxisReleased(int setindex, int axis, int value); // SetAxis class - void setStickButtonClick(int setindex, int stick, int button); // SetStick class - void setStickButtonRelease(int setindex, int stick, int button); // SetStick class - void setDPadButtonClick(int setindex, int dpad, int button); // SetHat class - void setDPadButtonRelease(int setindex, int dpad, int button); // SetHat class + void setAssignmentSensorChanged(JoySensorDirection direction, JoySensorType sensor, int originset, int newset, int mode); + void setAssignmentDPadChanged(int button, int dpad, int originset, int newset, int mode); // SetHat class + void setAssignmentVDPadChanged(int button, int dpad, int originset, int newset, int mode); // SetVDPad class + void setAssignmentAxisThrottleChanged(int axis, int originset); // SetAxis class + void setButtonClick(int index, int button); // SetButton class + void setButtonRelease(int index, int button); // SetButton class + void setAxisButtonClick(int setindex, int axis, int button); // SetAxis class + void setAxisButtonRelease(int setindex, int axis, int button); // SetAxis class + void setAxisActivated(int setindex, int axis, int value); // SetAxis class + void setAxisReleased(int setindex, int axis, int value); // SetAxis class + void setStickButtonClick(int setindex, int stick, int button); // SetStick class + void setStickButtonRelease(int setindex, int stick, int button); // SetStick class + void setSensorActivated(int setindex, JoySensorType type, int value); + void setSensorReleased(int setindex, JoySensorType, int value); + void setSensorButtonClick(int setindex, JoySensorType type, JoySensorDirection direction); + void setSensorButtonRelease(int setindex, JoySensorType type, JoySensorDirection direction); + void setDPadButtonClick(int setindex, int dpad, int button); // SetHat class + void setDPadButtonRelease(int setindex, int dpad, int button); // SetHat class void setButtonNameChange(int index); // SetButton class void setAxisButtonNameChange(int axisIndex, int buttonIndex); // SetAxis class void setStickButtonNameChange(int stickIndex, int buttonIndex); // SetStick class + void setSensorButtonNameChange(JoySensorType type, JoySensorDirection direction); void setDPadButtonNameChange(int dpadIndex, int buttonIndex); // SetHat class void setVDPadButtonNameChange(int vdpadIndex, int buttonIndex); // SetVDPad class void setAxisNameChange(int axisIndex); // SetAxis class void setStickNameChange(int stickIndex); // SetStick class + void setSensorNameChange(JoySensorType type); void setDPadNameChange(int dpadIndex); // SetHat class void setVDPadNameChange(int vdpadIndex); // SetVDPad class void propertyUpdated(); @@ -131,8 +152,9 @@ class SetJoystick : public SetJoystickXml void propogateSetButtonAssociation(int button, int newset, int mode); // SetButton class void propogateSetAxisButtonAssociation(int button, int axis, int newset, int mode); // SetAxis class void propogateSetStickButtonAssociation(int button, int stick, int newset, int mode); // SetStick class - void propogateSetDPadButtonAssociation(int button, int dpad, int newset, int mode); // SetHat class - void propogateSetVDPadButtonAssociation(int button, int dpad, int newset, int mode); // SetVDPad class + void propagateSetSensorButtonAssociation(JoySensorDirection direction, JoySensorType sensor, int newset, int mode); + void propogateSetDPadButtonAssociation(int button, int dpad, int newset, int mode); // SetHat class + void propogateSetVDPadButtonAssociation(int button, int dpad, int newset, int mode); // SetVDPad class void establishPropertyUpdatedConnection(); void disconnectPropertyUpdatedConnection(); @@ -144,19 +166,23 @@ class SetJoystick : public SetJoystickXml void propogateSetAxisButtonRelease(int button); // SetAxis class void propogateSetStickButtonClick(int button); // SetStick class void propogateSetStickButtonRelease(int button); // SetStick class - void propogateSetDPadButtonClick(int button); // SetHat class - void propogateSetDPadButtonRelease(int button); // SetHat class - void propogateSetAxisActivated(int value); // SetAxis class - void propogateSetAxisReleased(int value); // SetAxis class + void propagateSetSensorButtonClick(int button); + void propagateSetSensorButtonRelease(int button); + void propogateSetDPadButtonClick(int button); // SetHat class + void propogateSetDPadButtonRelease(int button); // SetHat class + void propogateSetAxisActivated(int value); // SetAxis class + void propogateSetAxisReleased(int value); // SetAxis class void propogateSetButtonNameChange(); // SetButton class void propogateSetAxisButtonNameChange(); // SetAxis class void propogateSetStickButtonNameChange(); // SetStick class + void propagateSetSensorButtonNameChange(); void propogateSetDPadButtonNameChange(); // SetHat class void propogateSetVDPadButtonNameChange(); // SetVDPad class void propogateSetAxisNameChange(); // SetAxis class void propogateSetStickNameChange(); // SetStick class + void propagateSetSensorNameChange(); void propogateSetDPadNameChange(); // SetHat class void propogateSetVDPadNameChange(); // SetVDPad class @@ -165,6 +191,7 @@ class SetJoystick : public SetJoystickXml QHash axes; QHash hats; QHash sticks; + QHash m_sensors; QHash vdpads; QList lastClickedButtons; diff --git a/src/uihelpers/joycontrolstickcontextmenuhelper.h b/src/uihelpers/joycontrolstickcontextmenuhelper.h index ee6b1417a..14d2af2d6 100644 --- a/src/uihelpers/joycontrolstickcontextmenuhelper.h +++ b/src/uihelpers/joycontrolstickcontextmenuhelper.h @@ -23,6 +23,10 @@ class JoyButtonSlot; +/** + * @brief Some helper methods which run in the IO thread and are called + * from the GUI thread. + */ class JoyControlStickContextMenuHelper : public QObject { Q_OBJECT diff --git a/src/uihelpers/joycontrolstickeditdialoghelper.h b/src/uihelpers/joycontrolstickeditdialoghelper.h index 7066f0518..ff2eda711 100644 --- a/src/uihelpers/joycontrolstickeditdialoghelper.h +++ b/src/uihelpers/joycontrolstickeditdialoghelper.h @@ -23,6 +23,10 @@ class JoyButtonSlot; +/** + * @brief Some helper methods which run in the IO thread and are called + * from the GUI thread. + */ class JoyControlStickEditDialogHelper : public QObject { Q_OBJECT diff --git a/src/uihelpers/joysensoriothreadhelper.cpp b/src/uihelpers/joysensoriothreadhelper.cpp new file mode 100644 index 000000000..dd71966c3 --- /dev/null +++ b/src/uihelpers/joysensoriothreadhelper.cpp @@ -0,0 +1,94 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "joysensoriothreadhelper.h" + +#include "joybuttonslot.h" +#include "joybuttontypes/joysensorbutton.h" +#include "joysensor.h" + +#include + +JoySensorIoThreadHelper::JoySensorIoThreadHelper(JoySensor *sensor, QObject *parent) + : QObject(parent) + , m_sensor(sensor) +{ + Q_ASSERT(m_sensor); +} + +/** + * @brief Sets pending slot values + * The values are applied to the buttons by calling setFromPendingSlots. + * @param[in] tempSlots QHash of new pending slot values. + * Buttons that are absent from the hash will be left unchanged. + */ +void JoySensorIoThreadHelper::setPendingSlots(QHash *tempSlots) +{ + m_pending_slots.clear(); + for (auto iter = tempSlots->cbegin(); iter != tempSlots->cend(); ++iter) + { + JoyButtonSlot *slot = iter.value(); + JoySensorDirection tempDir = iter.key(); + m_pending_slots.insert(tempDir, slot); + } +} + +/** + * @brief Clears pending slot values + */ +void JoySensorIoThreadHelper::clearPendingSlots() { m_pending_slots.clear(); } + +/** + * @brief Applies pending slot values to the buttons + * Should be called via QMetaObject::invokeMethod + */ +void JoySensorIoThreadHelper::setFromPendingSlots() +{ + if (!m_pending_slots.isEmpty()) + { + for (auto iter = m_pending_slots.cbegin(); iter != m_pending_slots.cend(); ++iter) + { + JoyButtonSlot *slot = iter.value(); + if (slot) + { + JoySensorDirection tempDir = iter.key(); + JoySensorButton *button = m_sensor->getDirectionButton(tempDir); + if (button) + { + button->clearSlotsEventReset(false); + button->setAssignedSlot(slot->getSlotCode(), slot->getSlotCodeAlias(), slot->getSlotMode()); + } + slot->deleteLater(); + } + } + } +} + +/** + * @brief Calls JoyButton::clearSlotEventReset on all JoyButtons of the underlying sensor. + * Should be called via QMetaObject::invokeMethod + */ +void JoySensorIoThreadHelper::clearButtonsSlotsEventReset() +{ + QHash *buttons = m_sensor->getButtons(); + for (auto iter = buttons->cbegin(); iter != buttons->cend(); ++iter) + { + JoySensorButton *button = iter.value(); + if (button) + button->clearSlotsEventReset(); + } +} diff --git a/src/uihelpers/joysensoriothreadhelper.h b/src/uihelpers/joysensoriothreadhelper.h new file mode 100644 index 000000000..e69d95e35 --- /dev/null +++ b/src/uihelpers/joysensoriothreadhelper.h @@ -0,0 +1,47 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include "joysensordirection.h" + +#include +#include + +class JoyButtonSlot; +class JoySensor; + +/** + * @brief Some helper methods which run in the IO thread and are called + * from the GUI thread. + */ +class JoySensorIoThreadHelper : public QObject +{ + Q_OBJECT + + public: + explicit JoySensorIoThreadHelper(JoySensor *sensor, QObject *parent = nullptr); + void setPendingSlots(QHash *tempSlots); + void clearPendingSlots(); + + public slots: + void setFromPendingSlots(); + void clearButtonsSlotsEventReset(); + + private: + JoySensor *m_sensor; + QHash m_pending_slots; +}; diff --git a/src/uihelpers/joytabwidgethelper.cpp b/src/uihelpers/joytabwidgethelper.cpp index 9f55a9b1d..60bc4147d 100644 --- a/src/uihelpers/joytabwidgethelper.cpp +++ b/src/uihelpers/joytabwidgethelper.cpp @@ -66,6 +66,9 @@ bool JoyTabWidgetHelper::hasError() { return errorOccurred; } QString JoyTabWidgetHelper::getErrorString() { return lastErrorString; } +/** + * @brief XML read entry point for the GUI + */ bool JoyTabWidgetHelper::readConfigFile(QString filepath) { bool result = false; @@ -101,6 +104,9 @@ bool JoyTabWidgetHelper::readConfigFileWithRevert(QString filepath) return readConfigFile(filepath); } +/** + * @brief XML write entry point for the GUI + */ bool JoyTabWidgetHelper::writeConfigFile(QString filepath) { bool result = false; diff --git a/src/xml/inputdevicexml.cpp b/src/xml/inputdevicexml.cpp index b4f64e8cf..5d72f0fd4 100644 --- a/src/xml/inputdevicexml.cpp +++ b/src/xml/inputdevicexml.cpp @@ -18,7 +18,9 @@ #include "inputdevicexml.h" #include "inputdevice.h" #include "joybuttontypes/joycontrolstickbutton.h" +#include "joybuttontypes/joysensorbutton.h" #include "joycontrolstick.h" +#include "joysensor.h" #include "vdpad.h" #include "common.h" @@ -34,6 +36,10 @@ InputDeviceXml::InputDeviceXml(InputDevice *inputDevice, QObject *parent) { } +/** + * @brief Deserializes the given XML stream into an InputDevice object + * @param[in] xml The XML stream to read from + */ void InputDeviceXml::readConfig(QXmlStreamReader *xml) { if (xml->isStartElement() && (xml->name() == m_inputDevice->getXmlName())) @@ -237,6 +243,14 @@ void InputDeviceXml::readConfig(QXmlStreamReader *xml) { m_inputDevice->setStickButtonName(index, buttonIndex, temp); } + } else if ((xml->name() == "sensorbuttonname") && xml->isStartElement()) + { + int type = xml->attributes().value("type").toString().toInt(); + int direction = xml->attributes().value("button").toString().toInt(); + QString temp = xml->readElementText(); + if (!temp.isEmpty()) + m_inputDevice->setSensorButtonName(static_cast(type), + static_cast(direction), temp); } else if ((xml->name() == "dpadbuttonname") && xml->isStartElement()) { int index = xml->attributes().value("index").toString().toInt(); @@ -279,6 +293,12 @@ void InputDeviceXml::readConfig(QXmlStreamReader *xml) { m_inputDevice->setStickName(index, temp); } + } else if ((xml->name() == "sensorname") && xml->isStartElement()) + { + int type = xml->attributes().value("type").toString().toInt(); + QString temp = xml->readElementText(); + if (!temp.isEmpty()) + m_inputDevice->setSensorName(static_cast(type), temp); } else if ((xml->name() == "dpadname") && xml->isStartElement()) { int index = xml->attributes().value("index").toString().toInt(); @@ -320,6 +340,18 @@ void InputDeviceXml::readConfig(QXmlStreamReader *xml) double offsetY = xml->attributes().value("offsety").toString().toDouble(); double gainY = xml->attributes().value("gainy").toString().toDouble(); m_inputDevice->applyStickCalibration(index, offsetX, gainX, offsetY, gainY); + } else if ((xml->name() == "accelerometer")) + { + double x0 = xml->attributes().value("orientationx").toString().toDouble(); + double y0 = xml->attributes().value("orientationy").toString().toDouble(); + double z0 = xml->attributes().value("orientationz").toString().toDouble(); + m_inputDevice->applyAccelerometerCalibration(x0, y0, z0); + } else if ((xml->name() == "gyroscope")) + { + double x0 = xml->attributes().value("offsetx").toString().toDouble(); + double y0 = xml->attributes().value("offsety").toString().toDouble(); + double z0 = xml->attributes().value("offsetz").toString().toDouble(); + m_inputDevice->applyGyroscopeCalibration(x0, y0, z0); } xml->skipCurrentElement(); xml->readNextStartElement(); @@ -348,6 +380,10 @@ void InputDeviceXml::readConfig(QXmlStreamReader *xml) } } +/** + * @brief Serializes an InputDevice object into the the given XML stream + * @param[in,out] xml The XML stream to write to + */ void InputDeviceXml::writeConfig(QXmlStreamWriter *xml) { xml->writeStartElement(m_inputDevice->getXmlName()); @@ -569,6 +605,38 @@ void InputDeviceXml::writeConfig(QXmlStreamWriter *xml) } } + // write sensors + auto sensors = m_inputDevice->getActiveSetJoystick()->getSensors(); + for (const auto &sensor : sensors) + { + if (sensor != nullptr) + { + if (!sensor->getSensorName().isEmpty()) + { + xml->writeStartElement("sensorname"); + xml->writeAttribute("type", QString::number(sensor->getType())); + xml->writeCharacters(sensor->getSensorName()); + xml->writeEndElement(); + } + + // write button of each sensor + auto buttons = sensor->getButtons(); + for (auto iter = buttons->cbegin(); iter != buttons->cend(); ++iter) + { + JoySensorButton *button = iter.value(); + + if (button && !button->getButtonName().isEmpty()) + { + xml->writeStartElement("sensorbuttonname"); + xml->writeAttribute("type", QString::number(sensor->getType())); + xml->writeAttribute("button", QString::number(button->getRealJoyNumber())); + xml->writeCharacters(button->getButtonName()); + xml->writeEndElement(); + } + } + } + } + // write Hats QListIterator currJoyDPad(m_inputDevice->getActiveSetJoystick()->getHats().values()); @@ -668,6 +736,29 @@ void InputDeviceXml::writeConfig(QXmlStreamWriter *xml) xml->writeEndElement(); } + JoySensor *accelerometer = m_inputDevice->getActiveSetJoystick()->getSensor(ACCELEROMETER); + if (accelerometer != nullptr && accelerometer->isCalibrated()) + { + double orientationX, orientationY, orientationZ; + accelerometer->getCalibration(&orientationX, &orientationY, &orientationZ); + xml->writeStartElement("accelerometer"); + xml->writeAttribute("orientationx", QString::number(orientationX)); + xml->writeAttribute("orientationy", QString::number(orientationY)); + xml->writeAttribute("orientationz", QString::number(orientationZ)); + xml->writeEndElement(); + } + + JoySensor *gyroscope = m_inputDevice->getActiveSetJoystick()->getSensor(GYROSCOPE); + if (gyroscope != nullptr && gyroscope->isCalibrated()) + { + double offsetX, offsetY, offsetZ; + gyroscope->getCalibration(&offsetX, &offsetY, &offsetZ); + xml->writeStartElement("gyroscope"); + xml->writeAttribute("offsetx", QString::number(offsetX)); + xml->writeAttribute("offsety", QString::number(offsetY)); + xml->writeAttribute("offsetz", QString::number(offsetZ)); + xml->writeEndElement(); + } xml->writeEndElement(); // xml->writeStartElement("sets"); diff --git a/src/xml/inputdevicexml.h b/src/xml/inputdevicexml.h index c020a1f05..04792fd4d 100644 --- a/src/xml/inputdevicexml.h +++ b/src/xml/inputdevicexml.h @@ -25,6 +25,14 @@ class QXmlStreamWriter; class InputDevice; class AntiMicroSettings; +/** + * @brief Generic InputDevice XML serialization/deserialization helper class + * Reads data from the supplied InputDevice object and writes it to XML or + * reads data from an QXmlStreamReader and writes it to the InputDevice object. + * + * After serializing or deserializing the device data, it reads/writes + * all SetJoysticks. + */ class InputDeviceXml : public QObject { Q_OBJECT diff --git a/src/xml/joybuttonxml.cpp b/src/xml/joybuttonxml.cpp index d84dd0552..40e8a3662 100644 --- a/src/xml/joybuttonxml.cpp +++ b/src/xml/joybuttonxml.cpp @@ -380,7 +380,7 @@ void JoyButtonXml::writeConfig(QXmlStreamWriter *xml) xml->writeTextElement("mousespringheight", QString::number(m_joyButton->getSpringHeight())); } - if (m_joyButton->getMouseCurve() != JoyButton::DEFAULTMOUSECURVE) + if (m_joyButton->getMouseCurve() != m_joyButton->getDefaultMouseCurve()) { switch (m_joyButton->getMouseCurve()) { diff --git a/src/xml/setjoystickxml.cpp b/src/xml/setjoystickxml.cpp index 9760f2352..376d41616 100644 --- a/src/xml/setjoystickxml.cpp +++ b/src/xml/setjoystickxml.cpp @@ -26,6 +26,7 @@ #include "joybutton.h" #include "joycontrolstick.h" #include "joydpad.h" +#include "joysensor.h" #include "vdpad.h" #include "setjoystick.h" @@ -42,6 +43,10 @@ SetJoystickXml::SetJoystickXml(SetJoystick *setJoystick, QObject *parent) { } +/** + * @brief Deserializes the given XML stream into a SetJoystick object + * @param[in] xml The XML stream to read from + */ void SetJoystickXml::readConfig(QXmlStreamReader *xml) { if (xml->isStartElement() && (xml->name() == "set")) @@ -97,6 +102,15 @@ void SetJoystickXml::readConfig(QXmlStreamReader *xml) { xml->skipCurrentElement(); } + } else if ((xml->name() == "sensor") && xml->isStartElement()) + { + int type = xml->attributes().value("type").toString().toInt(); + JoySensor *sensor = m_setJoystick->getSensor(static_cast(type)); + + if (sensor != nullptr) + sensor->readConfig(xml); + else + xml->skipCurrentElement(); } else if ((xml->name() == "vdpad") && xml->isStartElement()) { int index = xml->attributes().value("index").toString().toInt(); @@ -123,6 +137,10 @@ void SetJoystickXml::readConfig(QXmlStreamReader *xml) } } +/** + * @brief Serializes a SetJoystick object into the the given XML stream + * @param[in,out] xml The XML stream to write to + */ void SetJoystickXml::writeConfig(QXmlStreamWriter *xml) { if (!m_setJoystick->isSetEmpty()) @@ -138,6 +156,10 @@ void SetJoystickXml::writeConfig(QXmlStreamWriter *xml) while (i.hasNext()) i.next()->writeConfig(xml); + auto sensors = m_setJoystick->getSensors(); + for (const auto &sensor : sensors) + sensor->writeConfig(xml); + QList vdpadsList = m_setJoystick->getVdpads().values(); QListIterator vdpad(vdpadsList); while (vdpad.hasNext()) diff --git a/src/xml/setjoystickxml.h b/src/xml/setjoystickxml.h index c5a858092..d99808afe 100644 --- a/src/xml/setjoystickxml.h +++ b/src/xml/setjoystickxml.h @@ -26,6 +26,11 @@ class JoyButtonXml; class QXmlStreamReader; class QXmlStreamWriter; +/** + * @brief SetJoystick XML serialization/deserialization helper class + * Reads data from the supplied SetJoystick object and writes it to XML or + * reads data from an QXmlStreamReader and writes it to the SetJoystick object. + */ class SetJoystickXml : public QObject { Q_OBJECT diff --git a/src/xmlconfigreader.cpp b/src/xmlconfigreader.cpp index ce1bc0315..fa80b0153 100644 --- a/src/xmlconfigreader.cpp +++ b/src/xmlconfigreader.cpp @@ -35,6 +35,9 @@ #include #include +/** + * @brief Main XML config writer class + */ XMLConfigReader::XMLConfigReader(QObject *parent) : QObject(parent) { @@ -67,6 +70,9 @@ XMLConfigReader::~XMLConfigReader() void XMLConfigReader::setJoystick(InputDevice *joystick) { m_joystick = joystick; } +/** + * @brief Sets the filename of the to be read XML file + */ void XMLConfigReader::setFileName(QString filename) { QFile *temp = new QFile(filename); @@ -81,6 +87,10 @@ void XMLConfigReader::setFileName(QString filename) } } +/** + * @brief Read input device config from the current XML file into the InputDevice object + * @param[in,out] joystick InputDevice into which the config is read + */ void XMLConfigReader::configJoystick(InputDevice *joystick) { m_joystick = joystick; diff --git a/src/xmlconfigreader.h b/src/xmlconfigreader.h index 7b2f2bddf..6bde91154 100644 --- a/src/xmlconfigreader.h +++ b/src/xmlconfigreader.h @@ -28,6 +28,9 @@ class QXmlStreamReader; class InputDeviceXml; class QFile; +/** + * @brief Main XML config reader class + */ class XMLConfigReader : public QObject { Q_OBJECT diff --git a/src/xmlconfigwriter.cpp b/src/xmlconfigwriter.cpp index dfc20312b..3bc6ed2a9 100644 --- a/src/xmlconfigwriter.cpp +++ b/src/xmlconfigwriter.cpp @@ -56,6 +56,10 @@ XMLConfigWriter::~XMLConfigWriter() } } +/** + * @brief Write input device config from the current object into XML file + * @param[in] joystickXml InputDeviceXml which gets serialized + */ void XMLConfigWriter::write(InputDeviceXml *joystickXml) { writerError = false; @@ -81,6 +85,9 @@ void XMLConfigWriter::write(InputDeviceXml *joystickXml) configFile->close(); } +/** + * @brief Sets the filename of the to be written XML file + */ void XMLConfigWriter::setFileName(QString filename) { QFile *temp = new QFile(filename);