diff --git a/build_scripts/qt/sources.pri b/build_scripts/qt/sources.pri index f088184..7e8a84a 100644 --- a/build_scripts/qt/sources.pri +++ b/build_scripts/qt/sources.pri @@ -15,10 +15,11 @@ SOURCES += src/main.cpp\ src/openvr/ivrinput.cpp \ src/utils/setup.cpp \ src/utils/paths.cpp \ - src/utils/FrameRateUtils.cpp \ + src/utils/FrameRateUtils.cpp \ src/openvr/overlay_utils.cpp \ src/keyboard_input/keyboard_input.cpp \ - src/keyboard_input/input_parser.cpp + src/keyboard_input/input_parser.cpp \ + src/settings/settings.cpp HEADERS += src/overlaycontroller.h \ src/tabcontrollers/AudioTabController.h \ @@ -46,10 +47,15 @@ HEADERS += src/overlaycontroller.h \ src/openvr/ivrinput.h \ src/utils/setup.h \ src/utils/paths.h \ - src/utils/FrameRateUtils.h \ + src/utils/FrameRateUtils.h \ src/openvr/overlay_utils.h \ src/keyboard_input/input_parser.h \ - src/keyboard_input/input_sender.h + src/keyboard_input/input_sender.h \ + src/settings/settings.h \ + src/settings/setting_value.h \ + src/settings/settings_internal.h \ + src/settings/settings_controller.h \ + src/settings/specific_setting_value.h win32 { SOURCES += src/tabcontrollers/audiomanager/AudioManagerWindows.cpp \ diff --git a/docs/specs/Settings-API.md b/docs/specs/Settings-API.md new file mode 100644 index 0000000..c189f30 --- /dev/null +++ b/docs/specs/Settings-API.md @@ -0,0 +1,228 @@ +## Problem + +The current solution for storing and getting settings is to directly use the Qt QSettings object. +This leads to extremely verbose code for something that really should just be a single function call. + +While the Qt settings management abstracts low level details very well, an even higher level API should be sought after. + +The current system has stringly typed settings, which means it's possible to save the `desktopWidth` setting, but attempt to use the `deskWidth` setting somewhere else without noticing. + +Currently, there are many different ways for a variable to be initialized depending on what goes wrong (QML, header file initialization, constructor, QSettings loading). +There should only default option when none other are available. + +## Considerations for the new Settings API + +The public facing API should be decoupled from the Qt API, in case we want to use another API later. + +There should be a single correct way of referring to a setting. +The public API should not concern itself with how the settings are stored (.ini file/database/PROM). + +The public API should be simple, and return error checked, ready values. + +The settings should be logically grouped. + +Ideally, the type system is used to ensure that a setting isn't used as the wrong type (at compile time?). + +Ideally, it should not be possible to attempt to retrieve non-existent settings. + +## Requirements + +The API MUST transparently allow the storage of persistent values. The performance impact during runtime SHOULD be as small as possible. + +Settings set using the API MUST be recoverable in the event of an unexpected program termination. + +The API MUST allow for easy addition of new settings, with minimal vectors for error. + +The API MUST type check the values, so that it is not possible to misinterpret data. + +The API MUST have a default value for all settings that is used in case previously stored values are unavailable. + +## Public API + +The public API is in the `settings.h` file in the `settings` namespace. + +The settings are represented as enums according to type. +Each option is prefixed with their general category (`PLAYSPACE_`, etc.). +```cpp +enum class BoolSetting +{ + PLAYSPACE_lockXToggle, + PLAYSPACE_lockYToggle, + PLAYSPACE_lockZToggle, + + [...] + + CHAPERONE_disableChaperone, + // LAST_ENUMERATOR must always be set to the last value + LAST_ENUMERATOR = CHAPERONE_disableChaperone, +}; + +enum class DoubleSetting +{ + UTILITY_desktopWidth, + + [...] + + CHAPERONE_fadeDistanceRemembered, + // LAST_ENUMERATOR must always be set to the last value + LAST_ENUMERATOR = CHAPERONE_fadeDistanceRemembered, +}; + +enum class StringSetting +{ + KEYBOARDSHORTCUT_keyboardOne, + KEYBOARDSHORTCUT_keyboardTwo, + KEYBOARDSHORTCUT_keyboardThree, + // LAST_ENUMERATOR must always be set to the last value + LAST_ENUMERATOR = KEYBOARDSHORTCUT_keyboardThree, +}; + +enum class IntSetting +{ + UTILITY_alarmHour, + UTILITY_alarmMinute, + // LAST_ENUMERATOR must always be set to the last value + LAST_ENUMERATOR = UTILITY_alarmMinute, +}; +``` +The order of the of the enumerators matter, and they can't be reordered without leading to a runtime error. +The `LAST_ENUMERATOR` is a result of C++ enums not knowing how many enumerators they have, and it is required for the implementation to work correctly. + +Values can be accessed with imperative functions. +```cpp +[[nodiscard]] bool getSetting( const BoolSetting setting ); +void setSetting( const BoolSetting setting, const bool value ); + +[[nodiscard]] double getSetting( const DoubleSetting setting ); +void setSetting( const DoubleSetting setting, const double value ); + +[[nodiscard]] int getSetting( const IntSetting setting ); +void setSetting( const IntSetting setting, const int value ); + +[[nodiscard]] std::string getSetting( const StringSetting setting ); +void setSetting( const StringSetting setting, const std::string value ); +``` +The `[[nodiscard]]` means that it's a compiler error to not use the return value of the function. +Since none of the getters have side effects it would always be wrong to call the function without using the value. + +Additionally, there are three utility functions. +```cpp +std::string initializeAndGetSettingsPath(); + +void saveChangedSettings(); + +void saveAllSettings(); +``` +`saveChangedSettings()` only writes settings to disk that have had their values changed through the API. +`saveAllSettings()` writes all known settings to disk. + +`initializeAndGetSettingsPath()` initializes the API and returns the full path of the settings file. +It is not a requirement for this function to be called for the API to work. +If it is not called, the API will be initialized with the first call to the API. +The function is used as a means of controlling when the API will be initialized, as well as getting the current file path. + +## Implementation + +The public imperative API is an abstraction for the implementation, which is contained in a `SettingsController`. +The `SettingsController` object is a `static` object inside the `getSettingsController()` function. + +This is done to remove the limitations that an object oriented design would bring, namely lifetime and scope management. +Since the API is supposed to be globally available throughout the lifetime of the program, creating it as an object in the `OverlayController` and passing it to subcontrollers would only introduce unnecessary parameter passing. +The `SettingsController` also relies on containing the settings as mutable state, and doing so in a purely imperative way would be cumbersome. + +The API currently supports `int`, `double`, `std::string` and `bool` values. Below the `int` version is examined but the others are identical in implementation. + +Settings are stored as an array of `*SettingValue`s the size of the `*Setting` enum. +```cpp +constexpr static auto intSettingsSize + = static_cast( IntSetting::LAST_ENUMERATOR ) + 1; + +std::array m_intSettings{ + IntSettingValue{ IntSetting::UTILITY_alarmHour, + SettingCategory::Utility, + QtInfo{ "alarmHour" }, + 0 }, + IntSettingValue{ IntSetting::UTILITY_alarmMinute, + SettingCategory::Utility, + QtInfo{ "alarmMinute" }, + 0 }, +}; +``` +Here, for each setting, the following is defined: +* `setting`: Which enumerator the setting has. +* `category`: Which category the setting belongs to (used for QSettings categories). +* `QtInfo`: A struct that contains Qt specific information. For now only a string value containing the name of the setting. +* The default value of the setting. + +The `*SettingValue` classes are template specializations of the `SpecificSettingValue` class. +```cpp +using IntSettingValue = SpecificSettingValue; +``` + +The `SpecificSettingValue` class contains getters for the `setting`, `category` and `qtInfo` fields, as well as a getter+setter for the `value` field. +```cpp +template +class SpecificSettingValue : public SettingValue +{ +public: + SpecificSettingValue( const Setting setting, + const SettingCategory category, + const QtInfo qtInfo, + const Value defaultValue ) + : SettingValue( category, qtInfo ), m_setting( setting ), + m_value( defaultValue ) +{ + // Attempt to load saved setting + // Use default if none saved + // Save setting if not saved to make it appear in .ini file +} +``` +The templates used are not very complex. For the `IntSettingValue` you would simply mentally replace `Setting` with `IntSetting` and `Value` with `int`. +The templates reduce code duplication by a very large amount, which is why they are used. + +The `SpecificSettingValue` inherits from the `SettingValue` base class. This class only contains fields for `category` and `qtInfo`. +The inheritance from `SettingValue` is necessary in order to make the `saveChangedValues()` easier to implement. + +When a setting has changed value it is added to an array of `SettingValue`s (the base class). +It is possible to call `saveValue()` on all derived classes since it has been defined as a `virtual` function in the base class. +This is done in the `saveChangedValues()` function. + +Templates might seem daunting if when they are encountered for the first time, but the usage here is not very advanced. +```cpp +template +[[nodiscard]] ReturnType getSetting( const Setting setting ) const noexcept +{ + const auto index = static_cast( setting ); + + if constexpr ( std::is_same::value ) + { + return m_boolSettings[index].value(); + } + else if constexpr ( std::is_same::value ) + { + return m_doubleSettings[index].value(); + } + else if constexpr ( std::is_same::value ) + { + return m_intSettings[index].value(); + } + else if constexpr ( std::is_same::value ) + { + return m_stringSettings[index].value(); + } +} +``` +The above is the generalized `getSetting()` function that takes a generalized `Setting` as a parameter, and returns a generalized `ReturnType`. + +Following the `IntSettingValue` example we would pass an `IntSetting` enum to `Setting` and `int` to `ReturnType`. + +All our enums can be cast as integers, so we do that outside of the big `if` block. + +Next we need to specify which array we index in to get the relevant setting, so we use `if constexpr` and `std::is_same::value` to find which type we are dealing with. +This will compile different functions for the different types, so the `if constexpr` statement would not get executed. + +The alternative to this would be having `n` amount of specific functions with predefined types. +This is done in the public API for explicitness, but internally it is much easier to maintain a single function. + +The above function compiles down to the equivalent of four manually typed functions. + diff --git a/src/main.cpp b/src/main.cpp index d90e56b..4ac6407 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,5 @@ #include "utils/setup.h" +#include "settings/settings.h" INITIALIZE_EASYLOGGINGPP @@ -40,7 +41,7 @@ int main( int argc, char* argv[] ) mainEventLoop.applicationName() ); advsettings::OverlayController::setAppSettings( &appSettings ); LOG( INFO ) << "Settings File: " - << appSettings.fileName().toStdString(); + << settings::initializeAndGetSettingsPath(); QQmlEngine qmlEngine; diff --git a/src/overlaycontroller.cpp b/src/overlaycontroller.cpp index 532034f..4dbda5f 100644 --- a/src/overlaycontroller.cpp +++ b/src/overlaycontroller.cpp @@ -22,6 +22,7 @@ #include "utils/Matrix.h" #include "openvr/overlay_utils.h" #include "keyboard_input/input_sender.h" +#include "settings/settings.h" // application namespace namespace advsettings @@ -1114,6 +1115,9 @@ void OverlayController::mainEventLoop() LOG( INFO ) << "Received quit request."; vr::VRSystem()->AcknowledgeQuit_Exiting(); // Let us buy some // time just in case + + settings::saveAllSettings(); + m_moveCenterTabController.shutdown(); // Un-mute mic before Exiting VR, as it is set at system level Not // Vr level. @@ -1140,6 +1144,7 @@ void OverlayController::mainEventLoop() { LOG( DEBUG ) << "Dashboard deactivated"; m_dashboardVisible = false; + settings::saveChangedSettings(); } break; diff --git a/src/settings/bool_setting_value.h b/src/settings/bool_setting_value.h new file mode 100644 index 0000000..8aad5fd --- /dev/null +++ b/src/settings/bool_setting_value.h @@ -0,0 +1,63 @@ +#pragma once +#include +#include "settings.h" +#include "settings_internal.h" +#include "setting_value.h" + +namespace settings +{ +class BoolSettingValue : public SettingValue +{ +public: + BoolSettingValue( const BoolSetting setting, + const SettingCategory category, + const QtInfo qtInfo, + const bool defaultValue ) + : SettingValue( category, qtInfo ), m_setting( setting ), + m_value( defaultValue ) + { + const auto v = getQtSetting( SettingValue::category(), + SettingValue::qtInfo().settingName ); + if ( isValidQVariant( v, QMetaType::Bool ) ) + { + m_value = v.toBool(); + } + } + + void setValue( const bool value ) noexcept + { + m_value = value; + } + + bool value() const noexcept + { + return m_value; + } + + BoolSetting setting() const noexcept + { + return m_setting; + } + + SettingCategory category() const noexcept + { + return SettingValue::category(); + } + + QtInfo qtInfo() const noexcept + { + return SettingValue::qtInfo(); + } + + void saveValue() override + { + saveQtSetting( SettingValue::category(), + SettingValue::qtInfo().settingName, + m_value ); + } + +private: + const BoolSetting m_setting; + bool m_value; +}; +} // namespace settings diff --git a/src/settings/double_setting_value.h b/src/settings/double_setting_value.h new file mode 100644 index 0000000..947a482 --- /dev/null +++ b/src/settings/double_setting_value.h @@ -0,0 +1,63 @@ +#pragma once +#include +#include "settings.h" +#include "settings_internal.h" +#include "setting_value.h" + +namespace settings +{ +class DoubleSettingValue : public SettingValue +{ +public: + DoubleSettingValue( const DoubleSetting setting, + const SettingCategory category, + const QtInfo qtInfo, + const double defaultValue ) + : SettingValue( category, qtInfo ), m_setting( setting ), + m_value( defaultValue ) + { + const auto v = getQtSetting( SettingValue::category(), + SettingValue::qtInfo().settingName ); + if ( isValidQVariant( v, QMetaType::Double ) ) + { + m_value = v.toDouble(); + } + } + + void setValue( const double value ) noexcept + { + m_value = value; + } + + double value() const noexcept + { + return m_value; + } + + DoubleSetting setting() const noexcept + { + return m_setting; + } + + SettingCategory category() const noexcept + { + return SettingValue::category(); + } + + QtInfo qtInfo() const noexcept + { + return SettingValue::qtInfo(); + } + + void saveValue() override + { + saveQtSetting( SettingValue::category(), + SettingValue::qtInfo().settingName, + m_value ); + } + +private: + const DoubleSetting m_setting; + double m_value; +}; +} // namespace settings diff --git a/src/settings/setting_value.h b/src/settings/setting_value.h new file mode 100644 index 0000000..5b6ccf6 --- /dev/null +++ b/src/settings/setting_value.h @@ -0,0 +1,33 @@ +#pragma once +#include "settings_internal.h" + +namespace settings +{ +class SettingValue +{ +public: + SettingValue( const SettingCategory category, const QtInfo qtInfo ) noexcept + : m_category{ category }, m_qtInfo{ qtInfo } + { + } + + virtual ~SettingValue() {} + + [[nodiscard]] SettingCategory category() const noexcept + { + return m_category; + } + + [[nodiscard]] QtInfo qtInfo() const noexcept + { + return m_qtInfo; + } + + virtual void saveValue() = 0; + +private: + const SettingCategory m_category; + const QtInfo m_qtInfo; +}; + +} // namespace settings diff --git a/src/settings/settings.cpp b/src/settings/settings.cpp new file mode 100644 index 0000000..7deeed7 --- /dev/null +++ b/src/settings/settings.cpp @@ -0,0 +1,76 @@ +#include +#include +#include +#include "../overlaycontroller.h" +#include "settings_controller.h" + +namespace settings +{ +SettingsController& getSettingsController() +{ + static SettingsController s{}; + + return s; +} + +void saveChangedSettings() +{ + getSettingsController().saveChangedSettings(); +} + +void saveAllSettings() +{ + LOG( INFO ) << "Saving all settings."; + getSettingsController().saveAllSettings(); + LOG( INFO ) << "All settings saved."; +} + +[[nodiscard]] bool getSetting( const BoolSetting setting ) +{ + return getSettingsController().getSetting( setting ); +} + +void setSetting( const BoolSetting setting, const bool value ) +{ + getSettingsController().setSetting( setting, value ); +} + +[[nodiscard]] double getSetting( const DoubleSetting setting ) +{ + return getSettingsController().getSetting( setting ); +} + +void setSetting( const DoubleSetting setting, const double value ) +{ + getSettingsController().setSetting( setting, value ); +} + +[[nodiscard]] int getSetting( const IntSetting setting ) +{ + return getSettingsController().getSetting( setting ); +} + +void setSetting( const IntSetting setting, const int value ) +{ + getSettingsController().setSetting( setting, value ); +} + +[[nodiscard]] std::string getSetting( const StringSetting setting ) +{ + return getSettingsController().getSetting( setting ); +} + +void setSetting( const StringSetting setting, const std::string value ) +{ + getSettingsController().setSetting( setting, value ); +} + +std::string initializeAndGetSettingsPath() +{ + // The static object is initialized the first time the function is called. + // Having a dedicated function for this allows more control over when this + // happens. + return getSettingsController().getSettingsFileName(); +} + +} // namespace settings diff --git a/src/settings/settings.h b/src/settings/settings.h new file mode 100644 index 0000000..bd8412c --- /dev/null +++ b/src/settings/settings.h @@ -0,0 +1,93 @@ +#pragma once +#include + +namespace settings +{ +enum class BoolSetting +{ + PLAYSPACE_lockXToggle, + PLAYSPACE_lockYToggle, + PLAYSPACE_lockZToggle, + PLAYSPACE_momentumSave, + PLAYSPACE_turnBindLeft, + PLAYSPACE_turnBindRight, + PLAYSPACE_turnBounds, + PLAYSPACE_moveShortcutLeft, + PLAYSPACE_moveShortcutRight, + PLAYSPACE_dragBounds, + PLAYSPACE_allowExternalEdits, + PLAYSPACE_oldStyleMotion, + PLAYSPACE_universeCenteredRotation, + PLAYSPACE_enableSeatedMotion, + + APPLICATION_disableVersionCheck, + APPLICATION_previousShutdownSafe, + + VIDEO_brightnessEnabled, + VIDEO_isOverlayMethodActive, + VIDEO_colorOverlayEnabled, + + CHAPERONE_disableChaperone, + // LAST_ENUMERATOR must always be set to the last value + LAST_ENUMERATOR = CHAPERONE_disableChaperone, +}; + +enum class DoubleSetting +{ + UTILITY_desktopWidth, + + VIDEO_brightnessOpacityValue, + VIDEO_brightnessValue, + VIDEO_colorOverlayOpacity, + VIDEO_colorRed, + VIDEO_colorGreen, + VIDEO_colorBlue, + VIDEO_colorRedNew, + VIDEO_colorGreenNew, + VIDEO_colorBlueNew, + + CHAPERONE_switchToBeginnerDistance, + CHAPERONE_hapticFeedbackDistance, + CHAPERONE_alarmSoundDistance, + CHAPERONE_showDashboardDistance, + CHAPERONE_fadeDistanceRemembered, + // LAST_ENUMERATOR must always be set to the last value + LAST_ENUMERATOR = CHAPERONE_fadeDistanceRemembered, +}; + +enum class StringSetting +{ + KEYBOARDSHORTCUT_keyboardOne, + KEYBOARDSHORTCUT_keyboardTwo, + KEYBOARDSHORTCUT_keyboardThree, + // LAST_ENUMERATOR must always be set to the last value + LAST_ENUMERATOR = KEYBOARDSHORTCUT_keyboardThree, +}; + +enum class IntSetting +{ + UTILITY_alarmHour, + UTILITY_alarmMinute, + // LAST_ENUMERATOR must always be set to the last value + LAST_ENUMERATOR = UTILITY_alarmMinute, +}; + +std::string initializeAndGetSettingsPath(); + +void saveChangedSettings(); + +void saveAllSettings(); + +[[nodiscard]] bool getSetting( const BoolSetting setting ); +void setSetting( const BoolSetting setting, const bool value ); + +[[nodiscard]] double getSetting( const DoubleSetting setting ); +void setSetting( const DoubleSetting setting, const double value ); + +[[nodiscard]] int getSetting( const IntSetting setting ); +void setSetting( const IntSetting setting, const int value ); + +[[nodiscard]] std::string getSetting( const StringSetting setting ); +void setSetting( const StringSetting setting, const std::string value ); + +} // namespace settings diff --git a/src/settings/settings_controller.h b/src/settings/settings_controller.h new file mode 100644 index 0000000..9c63dbc --- /dev/null +++ b/src/settings/settings_controller.h @@ -0,0 +1,337 @@ +#pragma once +#include +#include +#include +#include +#include "settings.h" +#include "setting_value.h" +#include "../utils/setup.h" +#include "specific_setting_value.h" + +namespace settings +{ +template +void verifySettings( std::array v ) noexcept +{ + for ( int settingIndex = 0; settingIndex < ( ArraySize - 1 ); + ++settingIndex ) + { + // Having the enum values be in the correct location makes getting a + // value O(1) instead of O(n) due to array lookup vs traversal + // Also ensures that there are no mising values + + const auto currentSetting + = v[static_cast( settingIndex )].setting(); + + const auto settingCorrectlyLocated + = currentSetting == static_cast( settingIndex ); + if ( !settingCorrectlyLocated ) + { + const auto enumName = typeid( Enum ).name(); + + const auto enumSettingNumber = static_cast( currentSetting ); + + LOG( ERROR ) << enumName << ": at index '" << settingIndex + << "' is incorrect. Current value: '" + << enumSettingNumber + << "'. Exiting " + "with error code " + << ReturnErrorCode::SETTING_INCORRECT_INDEX << "."; + exit( ReturnErrorCode::SETTING_INCORRECT_INDEX ); + } + } + + LOG( DEBUG ) << "Settings for '" << typeid( Enum ).name() << "' verified."; +} + +class SettingsController +{ +public: + SettingsController() + { + verifySettings( m_boolSettings ); + + verifySettings( m_doubleSettings ); + + verifySettings( m_stringSettings ); + + verifySettings( m_intSettings ); + } + + std::string getSettingsFileName() const noexcept + { + return getQSettings().fileName().toStdString(); + } + + void saveChangedSettings() + { + if ( m_changedSettings.empty() ) + { + return; + } + + for ( const auto setting : m_changedSettings ) + { + setting->saveValue(); + } + + m_changedSettings.clear(); + } + + void saveAllSettings() + { + for ( auto& setting : m_boolSettings ) + { + setting.saveValue(); + } + } + + template + [[nodiscard]] ReturnType getSetting( const Setting setting ) const noexcept + { + const auto index = static_cast( setting ); + + if constexpr ( std::is_same::value ) + { + return m_boolSettings[index].value(); + } + else if constexpr ( std::is_same::value ) + { + return m_doubleSettings[index].value(); + } + else if constexpr ( std::is_same::value ) + { + return m_intSettings[index].value(); + } + else if constexpr ( std::is_same::value ) + { + return m_stringSettings[index].value(); + } + } + + template + void setSetting( const Setting setting, const Type value ) noexcept + { + const auto index = static_cast( setting ); + + if constexpr ( std::is_same::value ) + { + auto& s = m_boolSettings[index]; + s.setValue( value ); + + m_changedSettings.push_back( &s ); + } + else if constexpr ( std::is_same::value ) + { + auto& s = m_doubleSettings[index]; + s.setValue( value ); + + m_changedSettings.push_back( &s ); + } + else if constexpr ( std::is_same::value ) + { + auto& s = m_intSettings[index]; + s.setValue( value ); + + m_changedSettings.push_back( &s ); + } + else if constexpr ( std::is_same::value ) + { + auto& s = m_stringSettings[index]; + s.setValue( value ); + + m_changedSettings.push_back( &s ); + } + } + +private: + std::vector m_changedSettings{}; + + constexpr static auto boolSettingSize + = static_cast( BoolSetting::LAST_ENUMERATOR ) + 1; + std::array m_boolSettings{ + BoolSettingValue{ BoolSetting::PLAYSPACE_lockXToggle, + SettingCategory::Playspace, + QtInfo{ "lockXToggle" }, + false }, + BoolSettingValue{ BoolSetting::PLAYSPACE_lockYToggle, + SettingCategory::Playspace, + QtInfo{ "lockYToggle" }, + false }, + BoolSettingValue{ BoolSetting::PLAYSPACE_lockZToggle, + SettingCategory::Playspace, + QtInfo{ "lockZToggle" }, + false }, + BoolSettingValue{ BoolSetting::PLAYSPACE_momentumSave, + SettingCategory::Playspace, + QtInfo{ "momentumSave" }, + false }, + BoolSettingValue{ BoolSetting::PLAYSPACE_turnBindLeft, + SettingCategory::Playspace, + QtInfo{ "turnBindLeft" }, + false }, + BoolSettingValue{ BoolSetting::PLAYSPACE_turnBindRight, + SettingCategory::Playspace, + QtInfo{ "turnBindRight" }, + false }, + BoolSettingValue{ BoolSetting::PLAYSPACE_turnBounds, + SettingCategory::Playspace, + QtInfo{ "turnBounds" }, + false }, + BoolSettingValue{ BoolSetting::PLAYSPACE_moveShortcutLeft, + SettingCategory::Playspace, + QtInfo{ "moveShortcutLeft" }, + false }, + BoolSettingValue{ BoolSetting::PLAYSPACE_moveShortcutRight, + SettingCategory::Playspace, + QtInfo{ "moveShortcutRight" }, + false }, + BoolSettingValue{ BoolSetting::PLAYSPACE_dragBounds, + SettingCategory::Playspace, + QtInfo{ "dragBounds" }, + false }, + BoolSettingValue{ BoolSetting::PLAYSPACE_allowExternalEdits, + SettingCategory::Playspace, + QtInfo{ "allowExternalEdits" }, + false }, + BoolSettingValue{ BoolSetting::PLAYSPACE_oldStyleMotion, + SettingCategory::Playspace, + QtInfo{ "oldStyleMotion" }, + false }, + BoolSettingValue{ BoolSetting::PLAYSPACE_universeCenteredRotation, + SettingCategory::Playspace, + QtInfo{ "universeCenteredRotation" }, + false }, + BoolSettingValue{ BoolSetting::PLAYSPACE_enableSeatedMotion, + SettingCategory::Playspace, + QtInfo{ "enableSeatedMotion" }, + false }, + + BoolSettingValue{ BoolSetting::APPLICATION_disableVersionCheck, + SettingCategory::Application, + QtInfo{ "disableVersionCheck" }, + false }, + BoolSettingValue{ BoolSetting::APPLICATION_previousShutdownSafe, + SettingCategory::Application, + QtInfo{ "previousShutdownSafe" }, + true }, + + BoolSettingValue{ BoolSetting::VIDEO_brightnessEnabled, + SettingCategory::Video, + QtInfo{ "brightnessEnabled" }, + false }, + BoolSettingValue{ BoolSetting::VIDEO_isOverlayMethodActive, + SettingCategory::Video, + QtInfo{ "isOverlayMethodActive" }, + false }, + BoolSettingValue{ BoolSetting::VIDEO_colorOverlayEnabled, + SettingCategory::Video, + QtInfo{ "colorOverlayEnabled" }, + false }, + + BoolSettingValue{ BoolSetting::CHAPERONE_disableChaperone, + SettingCategory::Chaperone, + QtInfo{ "disableChaperone" }, + false }, + }; + + constexpr static auto doubleSettingSize + = static_cast( DoubleSetting::LAST_ENUMERATOR ) + 1; + std::array m_doubleSettings{ + DoubleSettingValue{ DoubleSetting::UTILITY_desktopWidth, + SettingCategory::Utility, + QtInfo{ "desktopWidth" }, + 4.0 }, + + DoubleSettingValue{ DoubleSetting::VIDEO_brightnessOpacityValue, + SettingCategory::Video, + QtInfo{ "brightnessOpacityValue" }, + 0.0 }, + DoubleSettingValue{ DoubleSetting::VIDEO_brightnessValue, + SettingCategory::Video, + QtInfo{ "brightnessValue" }, + 1.0 }, + DoubleSettingValue{ DoubleSetting::VIDEO_colorOverlayOpacity, + SettingCategory::Video, + QtInfo{ "colorOverlayOpacity" }, + 0.0 }, + DoubleSettingValue{ DoubleSetting::VIDEO_colorRed, + SettingCategory::Video, + QtInfo{ "colorRed" }, + 1.0 }, + DoubleSettingValue{ DoubleSetting::VIDEO_colorGreen, + SettingCategory::Video, + QtInfo{ "colorGreen" }, + 1.0 }, + DoubleSettingValue{ DoubleSetting::VIDEO_colorBlue, + SettingCategory::Video, + QtInfo{ "colorBlue" }, + 1.0 }, + DoubleSettingValue{ DoubleSetting::VIDEO_colorRedNew, + SettingCategory::Video, + QtInfo{ "colorRedNew" }, + 1.0 }, + DoubleSettingValue{ DoubleSetting::VIDEO_colorGreenNew, + SettingCategory::Video, + QtInfo{ "colorGreenNew" }, + 1.0 }, + DoubleSettingValue{ DoubleSetting::VIDEO_colorBlueNew, + SettingCategory::Video, + QtInfo{ "colorBlueNew" }, + 1.0 }, + + DoubleSettingValue{ DoubleSetting::CHAPERONE_switchToBeginnerDistance, + SettingCategory::Video, + QtInfo{ "switchToBeginnerDistance" }, + 0.5 }, + DoubleSettingValue{ DoubleSetting::CHAPERONE_hapticFeedbackDistance, + SettingCategory::Video, + QtInfo{ "hapticFeedbackDistance" }, + 0.5 }, + DoubleSettingValue{ DoubleSetting::CHAPERONE_alarmSoundDistance, + SettingCategory::Video, + QtInfo{ "alarmSoundDistance" }, + 0.5 }, + DoubleSettingValue{ DoubleSetting::CHAPERONE_showDashboardDistance, + SettingCategory::Video, + QtInfo{ "showDashboardDistance" }, + 0.5 }, + DoubleSettingValue{ DoubleSetting::CHAPERONE_fadeDistanceRemembered, + SettingCategory::Chaperone, + QtInfo{ "fadeDistanceRemembered" }, + 0.5 }, + }; + + constexpr static auto stringSettingsSize + = static_cast( StringSetting::LAST_ENUMERATOR ) + 1; + constexpr static auto discordDefaultMuteKeybinding = "^>m"; + std::array m_stringSettings{ + StringSettingValue{ StringSetting::KEYBOARDSHORTCUT_keyboardOne, + SettingCategory::KeyboardShortcut, + QtInfo{ "keyboardOne" }, + discordDefaultMuteKeybinding }, + StringSettingValue{ StringSetting::KEYBOARDSHORTCUT_keyboardTwo, + SettingCategory::KeyboardShortcut, + QtInfo{ "keyboardTwo" }, + discordDefaultMuteKeybinding }, + StringSettingValue{ StringSetting::KEYBOARDSHORTCUT_keyboardThree, + SettingCategory::KeyboardShortcut, + QtInfo{ "keyboardThree" }, + discordDefaultMuteKeybinding }, + }; + + constexpr static auto intSettingsSize + = static_cast( IntSetting::LAST_ENUMERATOR ) + 1; + std::array m_intSettings{ + IntSettingValue{ IntSetting::UTILITY_alarmHour, + SettingCategory::Utility, + QtInfo{ "alarmHour" }, + 0 }, + IntSettingValue{ IntSetting::UTILITY_alarmMinute, + SettingCategory::Utility, + QtInfo{ "alarmMinute" }, + 0 }, + + }; +}; +} // namespace settings diff --git a/src/settings/settings_internal.h b/src/settings/settings_internal.h new file mode 100644 index 0000000..ddfc8b9 --- /dev/null +++ b/src/settings/settings_internal.h @@ -0,0 +1,90 @@ +#pragma once +#include +#include +#include +#include "../overlaycontroller.h" + +namespace settings +{ +enum class SettingCategory +{ + Utility, + KeyboardShortcut, + Playspace, + Application, + Video, + Chaperone, + ChaperoneProfiles, +}; + +struct QtInfo +{ + const std::string settingName; +}; + +[[nodiscard]] QSettings& getQSettings() +{ + static QSettings s( QSettings::IniFormat, + QSettings::UserScope, + application_strings::applicationOrganizationName, + application_strings::applicationName ); + + return s; +} + +[[nodiscard]] std::string getQtCategoryName( const SettingCategory category ) +{ + switch ( category ) + { + case SettingCategory::Utility: + return "utilitiesSettings"; + case SettingCategory::KeyboardShortcut: + return "keyboardShortcuts"; + case SettingCategory::Playspace: + return "playspaceSettings"; + case SettingCategory::Application: + return "applicationSettings"; + case SettingCategory::Video: + return "videoSettings"; + case SettingCategory::Chaperone: + return "chaperoneSettings"; + case SettingCategory::ChaperoneProfiles: + return "chaperoneProfiles"; + } + return "no-value"; +} + +[[nodiscard]] QVariant getQtSetting( const SettingCategory category, + const std::string qtSettingName ) +{ + getQSettings().beginGroup( getQtCategoryName( category ).c_str() ); + + const auto v = getQSettings().value( qtSettingName.c_str() ); + + getQSettings().endGroup(); + + return v; +} + +void saveQtSetting( const SettingCategory category, + const std::string qtSettingName, + const QVariant value ) +{ + getQSettings().beginGroup( getQtCategoryName( category ).c_str() ); + getQSettings().setValue( qtSettingName.c_str(), value ); + getQSettings().endGroup(); +} + +[[nodiscard]] bool isValidQVariant( const QVariant v, + const QMetaType::Type type ) +{ + const auto savedSettingIsValid + = v.isValid() && !v.isNull() && ( v.userType() == type ); + if ( savedSettingIsValid ) + { + return true; + } + return false; +} + +} // namespace settings diff --git a/src/settings/specific_setting_value.h b/src/settings/specific_setting_value.h new file mode 100644 index 0000000..6981afb --- /dev/null +++ b/src/settings/specific_setting_value.h @@ -0,0 +1,130 @@ +#pragma once +#include +#include "setting_value.h" +#include "settings.h" + +namespace settings +{ +template +class SpecificSettingValue : public SettingValue +{ +public: + SpecificSettingValue( const Setting setting, + const SettingCategory category, + const QtInfo qtInfo, + const Value defaultValue ) + : SettingValue( category, qtInfo ), m_setting( setting ), + m_value( defaultValue ) + { + // Only allow explicitly defined types to curtail unintended behavior + constexpr auto isBool = std::is_same::value; + constexpr auto isDouble = std::is_same::value; + constexpr auto isString = std::is_same::value; + constexpr auto isInt = std::is_same::value; + static_assert( isBool || isDouble || isString || isInt ); + + const auto v = getQtSetting( SettingValue::category(), + SettingValue::qtInfo().settingName ); + + QMetaType::Type type; + if constexpr ( std::is_same::value ) + { + type = QMetaType::Bool; + } + else if constexpr ( std::is_same::value ) + { + type = QMetaType::Double; + } + else if constexpr ( std::is_same::value ) + { + type = QMetaType::QString; + } + else if constexpr ( std::is_same::value ) + { + type = QMetaType::Int; + } + + if ( isValidQVariant( v, type ) ) + { + if constexpr ( std::is_same::value ) + { + m_value = v.toBool(); + } + + else if constexpr ( std::is_same::value ) + { + m_value = v.toDouble(); + } + + else if constexpr ( std::is_same::value ) + { + m_value = v.toString().toStdString(); + } + + else if constexpr ( std::is_same::value ) + { + m_value = v.toInt(); + } + } + + else + { + // If setting doesn't exist, create it automatically + saveValue(); + } + } + + void setValue( const Value value ) noexcept + { + m_value = value; + } + + [[nodiscard]] Value value() const noexcept + { + return m_value; + } + + [[nodiscard]] Setting setting() const noexcept + { + return m_setting; + } + + [[nodiscard]] SettingCategory category() const noexcept + { + return SettingValue::category(); + } + + [[nodiscard]] QtInfo qtInfo() const noexcept + { + return SettingValue::qtInfo(); + } + + void saveValue() override + { + if constexpr ( !std::is_same::value ) + { + saveQtSetting( SettingValue::category(), + SettingValue::qtInfo().settingName, + m_value ); + } + else + { + // Special case for std::string because it can't be auto + // converted to QVariant + saveQtSetting( SettingValue::category(), + SettingValue::qtInfo().settingName, + m_value.c_str() ); + } + } + +private: + const Setting m_setting; + Value m_value; +}; + +using BoolSettingValue = SpecificSettingValue; +using DoubleSettingValue = SpecificSettingValue; +using IntSettingValue = SpecificSettingValue; +using StringSettingValue = SpecificSettingValue; + +} // namespace settings diff --git a/src/utils/setup.h b/src/utils/setup.h index 471db69..cd2d879 100644 --- a/src/utils/setup.h +++ b/src/utils/setup.h @@ -17,6 +17,7 @@ enum ReturnErrorCode SUCCESS = 0, GENERAL_FAILURE = -1, OPENVR_INIT_ERROR = -2, + SETTING_INCORRECT_INDEX = -3, }; struct CommandLineOptions