From 5accfd478a6ffdededbc46e77777f127d696f647 Mon Sep 17 00:00:00 2001 From: Vasily Date: Thu, 17 Jan 2019 19:35:30 +0100 Subject: [PATCH 01/25] Implement beta IVRInput system for testing In order to test new system: Build and deploy application. Copy both files from IVRInput/ dir into the bin/win64 folder. Change const std::string m_actionManifestPath in steamivrinput.h to be directly to your action_manifest.json file. Run and turn on a Vive controller. Press the menu button. If logs say "NEXT SONG SET!" it works. --- IVRInput/action_manifest.json | 30 ++++++++++++++ IVRInput/legacy_actions.json | 42 ++++++++++++++++++++ advancedSettings.pro | 7 ++-- src/overlaycontroller.cpp | 9 ++++- src/overlaycontroller.h | 3 ++ src/steamivrinput.cpp | 75 +++++++++++++++++++++++++++++++++++ src/steamivrinput.h | 22 ++++++++++ 7 files changed, 184 insertions(+), 4 deletions(-) create mode 100644 IVRInput/action_manifest.json create mode 100644 IVRInput/legacy_actions.json create mode 100644 src/steamivrinput.cpp create mode 100644 src/steamivrinput.h diff --git a/IVRInput/action_manifest.json b/IVRInput/action_manifest.json new file mode 100644 index 0000000..7accc3a --- /dev/null +++ b/IVRInput/action_manifest.json @@ -0,0 +1,30 @@ +{ + "default_bindings": [ + { + "controller_type": "vive_controller", + "binding_url": "legacy_actions.json" + } + ], + "actions": [ + { + "name": "/actions/main/in/PlayNextTrack", + "requirement": "optional", + "type": "boolean" + } + ], + "action_sets": [ + { + "name": "/actions/main", + "usage": "leftright" + } + ], + "localization" : [ + { + "language_tag": "en", + + "/actions/main" : "My Game Actions", + + "/actions/main/in/PlayNextTrack" : "Play Next Track" + } + ] +} \ No newline at end of file diff --git a/IVRInput/legacy_actions.json b/IVRInput/legacy_actions.json new file mode 100644 index 0000000..3e2094c --- /dev/null +++ b/IVRInput/legacy_actions.json @@ -0,0 +1,42 @@ +{ + "alias_info" : {}, + "app_key" : "matzman666.advancedsettings", + "bindings" : { + "/actions/main" : { + "sources" : [ + { + "inputs" : { + "click" : { + "output" : "/actions/main/in/playnexttrack" + } + }, + "mode" : "button", + "path" : "/user/hand/left/input/application_menu" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/main/in/playnexttrack" + } + }, + "mode" : "button", + "path" : "/user/hand/right/input/application_menu" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/main/in/playnexttrack" + } + }, + "mode" : "button", + "path" : "/user/hand/right/input/grip" + } + ] + } + }, + "controller_type" : "vive_controller", + "description" : "", + "name" : "My My Test configuration for Vive Controller", + "options" : {}, + "simulated_actions" : [] +} diff --git a/advancedSettings.pro b/advancedSettings.pro index e9d529d..9ed0a57 100644 --- a/advancedSettings.pro +++ b/advancedSettings.pro @@ -58,8 +58,8 @@ SOURCES += src/main.cpp\ src/utils/ChaperoneUtils.cpp \ src/tabcontrollers/audiomanager/AudioManagerDummy.cpp \ src/tabcontrollers/keyboardinput/KeyboardInputDummy.cpp \ - src/overlaycontroller/openvr_init.cpp - + src/overlaycontroller/openvr_init.cpp \ + src/steamivrinput.cpp HEADERS += src/overlaycontroller.h \ src/tabcontrollers/AudioTabController.h \ @@ -79,7 +79,8 @@ HEADERS += src/overlaycontroller.h \ src/utils/ChaperoneUtils.h \ src/tabcontrollers/audiomanager/AudioManagerDummy.h \ src/tabcontrollers/keyboardinput/KeyboardInputDummy.h \ - src/overlaycontroller/openvr_init.h + src/overlaycontroller/openvr_init.h \ + src/steamivrinput.h win32 { SOURCES += src/tabcontrollers/audiomanager/AudioManagerWindows.cpp \ diff --git a/src/overlaycontroller.cpp b/src/overlaycontroller.cpp index b8cd84b..170cdba 100644 --- a/src/overlaycontroller.cpp +++ b/src/overlaycontroller.cpp @@ -118,7 +118,7 @@ OverlayController::OverlayController( bool desktopMode, nullptr, "OpenVR Advanced Settings Overlay", "Is OpenVR running?" ); throw std::runtime_error( std::string( "No Overlay interface" ) ); } - + m_actions.Init( false ); // Init controllers m_steamVRTabController.initStage1(); m_chaperoneTabController.initStage1(); @@ -477,6 +477,13 @@ void OverlayController::OnTimeoutPumpEvents() if ( !vr::VRSystem() ) return; + m_actions.Loop(); + + if ( m_actions.nextSongSet() ) + { + LOG( INFO ) << "NEXT SONG SET!"; + } + vr::VREvent_t vrEvent; bool chaperoneDataAlreadyUpdated = false; while ( pollNextEvent( m_ulOverlayHandle, &vrEvent ) ) diff --git a/src/overlaycontroller.h b/src/overlaycontroller.h index 9cefa65..9442b06 100644 --- a/src/overlaycontroller.h +++ b/src/overlaycontroller.h @@ -39,6 +39,8 @@ #include "tabcontrollers/UtilitiesTabController.h" #include "tabcontrollers/AccessibilityTabController.h" +#include "steamivrinput.h" + // application namespace namespace advsettings { @@ -90,6 +92,7 @@ private: public: // I know it's an ugly hack to make them public to enable external // access, but I am too lazy to implement getters. + SteamIVRInput m_actions; SteamVRTabController m_steamVRTabController; ChaperoneTabController m_chaperoneTabController; MoveCenterTabController m_moveCenterTabController; diff --git a/src/steamivrinput.cpp b/src/steamivrinput.cpp new file mode 100644 index 0000000..d8269dc --- /dev/null +++ b/src/steamivrinput.cpp @@ -0,0 +1,75 @@ +#include "steamivrinput.h" +#include +#include + +void SteamIVRInput::Init( const bool init ) +{ + if ( init ) + { + auto initError = vr::VRInitError_None; + vr::VR_Init( &initError, vr::VRApplication_Overlay ); + if ( initError != vr::VRInitError_None ) + { + std::cout << "SteamVR init error."; + } + } + + auto error + = vr::VRInput()->SetActionManifestPath( m_actionManifestPath.c_str() ); + if ( error != vr::EVRInputError::VRInputError_None ) + { + std::cout << "Action manifest error\n"; + } + + // Get action handle + error = vr::VRInput()->GetActionHandle( "/actions/main/in/PlayNextTrack", + &m_nextSongHandler ); + if ( error != vr::EVRInputError::VRInputError_None ) + { + std::cout << "Handle error.\n"; + } + + error = vr::VRInput()->GetActionSetHandle( "/actions/main", + &m_mainSetHandler ); + if ( error != vr::EVRInputError::VRInputError_None ) + { + std::cout << "Handle error.\n"; + } + + m_activeActionSet.ulActionSet = m_mainSetHandler; + m_activeActionSet.ulRestrictedToDevice = vr::k_ulInvalidInputValueHandle; + m_activeActionSet.nPriority = 0; +} + +bool SteamIVRInput::nextSongSet() +{ + auto e = vr::VRInput()->GetDigitalActionData( + m_nextSongHandler, + &m_nextSongData, + sizeof( m_nextSongData ), + vr::k_ulInvalidInputValueHandle ); + + if ( e != vr::EVRInputError::VRInputError_None ) + { + std::cout << e; + std::cout << "GetDigitalAction error.\n"; + } + + if ( m_nextSongData.bActive ) + { + std::cout << "Action Set Active!\n"; + } + + return m_nextSongData.bState; +} + +void SteamIVRInput::Loop() +{ + auto error = vr::VRInput()->UpdateActionState( + &m_activeActionSet, sizeof( m_activeActionSet ), 1 ); + + if ( error != vr::EVRInputError::VRInputError_None ) + { + std::cout << "Loop error.\n"; + } +} diff --git a/src/steamivrinput.h b/src/steamivrinput.h new file mode 100644 index 0000000..dea0dac --- /dev/null +++ b/src/steamivrinput.h @@ -0,0 +1,22 @@ +#ifndef STEAMIVRINPUT_H +#define STEAMIVRINPUT_H + +#include + +class SteamIVRInput +{ +public: + void Init( const bool init ); + void Loop(); + bool nextSongSet(); + +private: + const std::string m_actionManifestPath + = "YOUR_PATH_HERE\\action_manifest.json"; + vr::VRActionHandle_t m_nextSongHandler = {}; + vr::VRActionSetHandle_t m_mainSetHandler = {}; + vr::VRActiveActionSet_t m_activeActionSet = {}; + vr::InputDigitalActionData_t m_nextSongData = {}; +}; + +#endif // STEAMIVRINPUT_H From 586ee558c416aa49b101f27a49595533e40eee51 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 17 Jan 2019 22:00:10 +0100 Subject: [PATCH 02/25] determine vrinput manifest path dynamically --- src/steamivrinput.cpp | 8 ++++++-- src/steamivrinput.h | 2 -- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/steamivrinput.cpp b/src/steamivrinput.cpp index d8269dc..57ba219 100644 --- a/src/steamivrinput.cpp +++ b/src/steamivrinput.cpp @@ -1,6 +1,7 @@ #include "steamivrinput.h" #include #include +#include void SteamIVRInput::Init( const bool init ) { @@ -14,8 +15,11 @@ void SteamIVRInput::Init( const bool init ) } } - auto error - = vr::VRInput()->SetActionManifestPath( m_actionManifestPath.c_str() ); + QString manifestPath + = QStandardPaths::locate( QStandardPaths::AppDataLocation, + QStringLiteral( "action_manifest.json" ) ); + auto error = vr::VRInput()->SetActionManifestPath( + manifestPath.toStdString().c_str() ); if ( error != vr::EVRInputError::VRInputError_None ) { std::cout << "Action manifest error\n"; diff --git a/src/steamivrinput.h b/src/steamivrinput.h index dea0dac..91e50f8 100644 --- a/src/steamivrinput.h +++ b/src/steamivrinput.h @@ -11,8 +11,6 @@ public: bool nextSongSet(); private: - const std::string m_actionManifestPath - = "YOUR_PATH_HERE\\action_manifest.json"; vr::VRActionHandle_t m_nextSongHandler = {}; vr::VRActionSetHandle_t m_mainSetHandler = {}; vr::VRActiveActionSet_t m_activeActionSet = {}; From 1585322c9d48a2b73328398f7912cb8d1c4fecb9 Mon Sep 17 00:00:00 2001 From: Vasily Date: Sat, 19 Jan 2019 13:51:41 +0100 Subject: [PATCH 03/25] Move action manifest files to src/package_files Putting them in the new directory makes the automatically get included for deployment. legacy_actions.json was not a good name for the file, since it actually contained default bindings for Vive controllers. vive_defaults.json better explains what it is. --- {IVRInput => src/package_files}/action_manifest.json | 2 +- .../package_files/default_action_manifests/vive_defaults.json | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename {IVRInput => src/package_files}/action_manifest.json (87%) rename IVRInput/legacy_actions.json => src/package_files/default_action_manifests/vive_defaults.json (100%) diff --git a/IVRInput/action_manifest.json b/src/package_files/action_manifest.json similarity index 87% rename from IVRInput/action_manifest.json rename to src/package_files/action_manifest.json index 7accc3a..0b1a38c 100644 --- a/IVRInput/action_manifest.json +++ b/src/package_files/action_manifest.json @@ -2,7 +2,7 @@ "default_bindings": [ { "controller_type": "vive_controller", - "binding_url": "legacy_actions.json" + "binding_url": "default_action_manifests/vive_defaults.json" } ], "actions": [ diff --git a/IVRInput/legacy_actions.json b/src/package_files/default_action_manifests/vive_defaults.json similarity index 100% rename from IVRInput/legacy_actions.json rename to src/package_files/default_action_manifests/vive_defaults.json From 8bcab9b2c09e13480fa663b8363c02269b8a9395 Mon Sep 17 00:00:00 2001 From: Vasily Date: Sat, 19 Jan 2019 16:14:05 +0100 Subject: [PATCH 04/25] Refactor ivrinput functions to be more generic --- advancedSettings.pro | 3 +- src/ivrinput/ivrinput_action.h | 42 ++++++++++++ src/overlaycontroller.cpp | 2 +- src/steamivrinput.cpp | 118 ++++++++++++++++++++------------- src/steamivrinput.h | 29 ++++++-- 5 files changed, 141 insertions(+), 53 deletions(-) create mode 100644 src/ivrinput/ivrinput_action.h diff --git a/advancedSettings.pro b/advancedSettings.pro index 9ed0a57..20b477a 100644 --- a/advancedSettings.pro +++ b/advancedSettings.pro @@ -80,7 +80,8 @@ HEADERS += src/overlaycontroller.h \ src/tabcontrollers/audiomanager/AudioManagerDummy.h \ src/tabcontrollers/keyboardinput/KeyboardInputDummy.h \ src/overlaycontroller/openvr_init.h \ - src/steamivrinput.h + src/steamivrinput.h \ + src/ivrinput/ivrinput_action.h win32 { SOURCES += src/tabcontrollers/audiomanager/AudioManagerWindows.cpp \ diff --git a/src/ivrinput/ivrinput_action.h b/src/ivrinput/ivrinput_action.h new file mode 100644 index 0000000..85d2463 --- /dev/null +++ b/src/ivrinput/ivrinput_action.h @@ -0,0 +1,42 @@ +#pragma once +#include +#include + +enum class ActionType +{ + Undefined, + Digital, +}; + +class Action +{ +public: + Action( const char* const actionName, const ActionType type ) + : m_name( actionName ), m_type( type ) + { + auto error = vr::VRInput()->GetActionHandle( actionName, &m_handle ); + + if ( error != vr::EVRInputError::VRInputError_None ) + { + LOG( ERROR ) << "Error getting handle for '" << actionName + << "'. OpenVR Error: " << error; + } + } + vr::VRActionHandle_t handle() + { + return m_handle; + } + const char* name() + { + return m_name; + } + ActionType type() + { + return m_type; + } + +private: + vr::VRActionHandle_t m_handle = 0; + const char* m_name; + ActionType m_type = ActionType::Undefined; +}; diff --git a/src/overlaycontroller.cpp b/src/overlaycontroller.cpp index 170cdba..288fba7 100644 --- a/src/overlaycontroller.cpp +++ b/src/overlaycontroller.cpp @@ -118,7 +118,7 @@ OverlayController::OverlayController( bool desktopMode, nullptr, "OpenVR Advanced Settings Overlay", "Is OpenVR running?" ); throw std::runtime_error( std::string( "No Overlay interface" ) ); } - m_actions.Init( false ); + m_actions.Init(); // Init controllers m_steamVRTabController.initStage1(); m_chaperoneTabController.initStage1(); diff --git a/src/steamivrinput.cpp b/src/steamivrinput.cpp index 57ba219..69edfda 100644 --- a/src/steamivrinput.cpp +++ b/src/steamivrinput.cpp @@ -2,43 +2,84 @@ #include #include #include +#include -void SteamIVRInput::Init( const bool init ) +void setActionManifestPath( const QString actionManifestPath ) { - if ( init ) + auto error = vr::VRInput()->SetActionManifestPath( + actionManifestPath.toStdString().c_str() ); + if ( error != vr::EVRInputError::VRInputError_None ) { - auto initError = vr::VRInitError_None; - vr::VR_Init( &initError, vr::VRApplication_Overlay ); - if ( initError != vr::VRInitError_None ) - { - std::cout << "SteamVR init error."; - } + LOG( ERROR ) << "Error setting action manifest path: " + << actionManifestPath << ". OpenVR Error: " << error; + } +} + +vr::VRActionHandle_t getActionHandle( const char* const action ) +{ + vr::VRActionHandle_t actionHandle = 0; + + auto error = vr::VRInput()->GetActionHandle( action, &actionHandle ); + + if ( error != vr::EVRInputError::VRInputError_None ) + { + LOG( ERROR ) << "Error getting handle for '" << action + << "'. OpenVR Error: " << error; } - QString manifestPath + return actionHandle; +} + +vr::VRActionSetHandle_t getActionSetHandle( const char* const set ) +{ + vr::VRActionSetHandle_t setHandle = 0; + + auto error = vr::VRInput()->GetActionSetHandle( set, &setHandle ); + if ( error != vr::EVRInputError::VRInputError_None ) + { + LOG( ERROR ) << "Error getting handle for '" << set + << "'. OpenVR Error: " << error; + } + + return setHandle; +} + +vr::InputDigitalActionData_t getDigitalActionData( vr::VRActionHandle_t handle ) +{ + vr::InputDigitalActionData_t handleData = {}; + + auto error = vr::VRInput()->GetDigitalActionData( + handle, + &handleData, + sizeof( handleData ), + vr::k_ulInvalidInputValueHandle ); + + if ( error != vr::EVRInputError::VRInputError_None ) + { + LOG( ERROR ) << "Error getting Digital Action Data for handle " + << handle << ". SteamVR Error: " << error; + } + + return handleData; +} + +bool isActionActivatedOnce( vr::VRActionHandle_t handle ) +{ + auto handleData = getDigitalActionData( handle ); + + return handleData.bState && handleData.bChanged; +} + +void SteamIVRInput::Init() +{ + QString actionManifestPath = QStandardPaths::locate( QStandardPaths::AppDataLocation, QStringLiteral( "action_manifest.json" ) ); - auto error = vr::VRInput()->SetActionManifestPath( - manifestPath.toStdString().c_str() ); - if ( error != vr::EVRInputError::VRInputError_None ) - { - std::cout << "Action manifest error\n"; - } + setActionManifestPath( actionManifestPath ); - // Get action handle - error = vr::VRInput()->GetActionHandle( "/actions/main/in/PlayNextTrack", - &m_nextSongHandler ); - if ( error != vr::EVRInputError::VRInputError_None ) - { - std::cout << "Handle error.\n"; - } + m_nextSongHandler = getActionHandle( input_strings::k_actionNextTrack ); - error = vr::VRInput()->GetActionSetHandle( "/actions/main", - &m_mainSetHandler ); - if ( error != vr::EVRInputError::VRInputError_None ) - { - std::cout << "Handle error.\n"; - } + m_mainSetHandler = getActionSetHandle( input_strings::k_setMain ); m_activeActionSet.ulActionSet = m_mainSetHandler; m_activeActionSet.ulRestrictedToDevice = vr::k_ulInvalidInputValueHandle; @@ -47,24 +88,7 @@ void SteamIVRInput::Init( const bool init ) bool SteamIVRInput::nextSongSet() { - auto e = vr::VRInput()->GetDigitalActionData( - m_nextSongHandler, - &m_nextSongData, - sizeof( m_nextSongData ), - vr::k_ulInvalidInputValueHandle ); - - if ( e != vr::EVRInputError::VRInputError_None ) - { - std::cout << e; - std::cout << "GetDigitalAction error.\n"; - } - - if ( m_nextSongData.bActive ) - { - std::cout << "Action Set Active!\n"; - } - - return m_nextSongData.bState; + return isActionActivatedOnce( m_nextSongHandler ); } void SteamIVRInput::Loop() @@ -74,6 +98,6 @@ void SteamIVRInput::Loop() if ( error != vr::EVRInputError::VRInputError_None ) { - std::cout << "Loop error.\n"; + LOG( ERROR ) << "Loop error."; } } diff --git a/src/steamivrinput.h b/src/steamivrinput.h index 91e50f8..06f0adc 100644 --- a/src/steamivrinput.h +++ b/src/steamivrinput.h @@ -1,15 +1,31 @@ -#ifndef STEAMIVRINPUT_H -#define STEAMIVRINPUT_H +#pragma once #include +#include "ivrinput/ivrinput_action.h" class SteamIVRInput { public: - void Init( const bool init ); + // SteamIVRInput(); + void Init(); void Loop(); bool nextSongSet(); + // These have been explicitly deleted to make sure there are no attempts at + // copying the class in weird ways. It is not worth defining what should + // happen on copy because it simply shouldn't be done. + // Destructor. There are not terminating calls for the IVRInput API, so it + // is unneeded. + //~SteamIVRInput() = delete; + //// Copy constructor + // SteamIVRInput( const SteamIVRInput& ) = delete; + //// Copy assignment + // SteamIVRInput& operator=( const SteamIVRInput& ) = delete; + //// Move constructor + // SteamIVRInput( SteamIVRInput&& ) = delete; + //// Move assignment + // SteamIVRInput& operator=( const SteamIVRInput&& ) = delete; + private: vr::VRActionHandle_t m_nextSongHandler = {}; vr::VRActionSetHandle_t m_mainSetHandler = {}; @@ -17,4 +33,9 @@ private: vr::InputDigitalActionData_t m_nextSongData = {}; }; -#endif // STEAMIVRINPUT_H +namespace input_strings +{ +constexpr auto k_actionNextTrack = "/actions/main/in/PlayNextTrack"; +constexpr auto k_setMain = "/actions/main"; + +} // namespace input_strings From d7e3df185b0d11a2a71ea3bcde9afa7de928ede1 Mon Sep 17 00:00:00 2001 From: Vasily Date: Sat, 19 Jan 2019 21:25:15 +0100 Subject: [PATCH 05/25] Refactor IVRInput logic into several classes Keeping the action name, handle and type together allows for better error messages and limited run time type checking, to ensure that the wrong function isn't called. It is necessary to initialize the manifest in a class, since the Actions depend on this being set. Several files have been moved around to be more consistent. --- advancedSettings.pro | 8 +- src/ivrinput/ivrinput_action.h | 15 ++-- src/ivrinput/ivrinput_action_set.h | 33 ++++++++ src/ivrinput/ivrinput_manifest.h | 44 ++++++++++ src/ivrinput/steamivrinput.cpp | 114 ++++++++++++++++++++++++++ src/ivrinput/steamivrinput.h | 50 +++++++++++ src/overlaycontroller.cpp | 5 +- src/overlaycontroller.h | 4 +- src/overlaycontroller/openvr_init.cpp | 4 + src/steamivrinput.cpp | 103 ----------------------- src/steamivrinput.h | 41 --------- 11 files changed, 264 insertions(+), 157 deletions(-) create mode 100644 src/ivrinput/ivrinput_action_set.h create mode 100644 src/ivrinput/ivrinput_manifest.h create mode 100644 src/ivrinput/steamivrinput.cpp create mode 100644 src/ivrinput/steamivrinput.h delete mode 100644 src/steamivrinput.cpp delete mode 100644 src/steamivrinput.h diff --git a/advancedSettings.pro b/advancedSettings.pro index 20b477a..333b26a 100644 --- a/advancedSettings.pro +++ b/advancedSettings.pro @@ -59,7 +59,7 @@ SOURCES += src/main.cpp\ src/tabcontrollers/audiomanager/AudioManagerDummy.cpp \ src/tabcontrollers/keyboardinput/KeyboardInputDummy.cpp \ src/overlaycontroller/openvr_init.cpp \ - src/steamivrinput.cpp + src/ivrinput/steamivrinput.cpp HEADERS += src/overlaycontroller.h \ src/tabcontrollers/AudioTabController.h \ @@ -80,8 +80,10 @@ HEADERS += src/overlaycontroller.h \ src/tabcontrollers/audiomanager/AudioManagerDummy.h \ src/tabcontrollers/keyboardinput/KeyboardInputDummy.h \ src/overlaycontroller/openvr_init.h \ - src/steamivrinput.h \ - src/ivrinput/ivrinput_action.h + src/ivrinput/ivrinput_action.h \ + src/ivrinput/ivrinput_manifest.h \ + src/ivrinput/ivrinput_action_set.h \ + src/ivrinput/steamivrinput.h win32 { SOURCES += src/tabcontrollers/audiomanager/AudioManagerWindows.cpp \ diff --git a/src/ivrinput/ivrinput_action.h b/src/ivrinput/ivrinput_action.h index 85d2463..f3fe439 100644 --- a/src/ivrinput/ivrinput_action.h +++ b/src/ivrinput/ivrinput_action.h @@ -2,10 +2,13 @@ #include #include +namespace input +{ enum class ActionType { Undefined, Digital, + Analog, }; class Action @@ -22,21 +25,23 @@ public: << "'. OpenVR Error: " << error; } } - vr::VRActionHandle_t handle() + vr::VRActionHandle_t handle() const noexcept { return m_handle; } - const char* name() + std::string name() const noexcept { return m_name; } - ActionType type() + ActionType type() const noexcept { return m_type; } private: vr::VRActionHandle_t m_handle = 0; - const char* m_name; - ActionType m_type = ActionType::Undefined; + const std::string m_name; + const ActionType m_type = ActionType::Undefined; }; + +} // namespace input diff --git a/src/ivrinput/ivrinput_action_set.h b/src/ivrinput/ivrinput_action_set.h new file mode 100644 index 0000000..66b35a0 --- /dev/null +++ b/src/ivrinput/ivrinput_action_set.h @@ -0,0 +1,33 @@ +#pragma once +#include +#include + +namespace input +{ +class ActionSet +{ +public: + ActionSet( const char* setName ) : m_name( setName ) + { + auto error = vr::VRInput()->GetActionSetHandle( setName, &m_handle ); + if ( error != vr::EVRInputError::VRInputError_None ) + { + LOG( ERROR ) << "Error getting handle for '" << setName + << "'. OpenVR Error: " << error; + } + } + std::string name() + { + return m_name; + } + vr::VRActionSetHandle_t handle() + { + return m_handle; + } + +private: + const std::string m_name; + vr::VRActionSetHandle_t m_handle = 0; +}; + +} // namespace input diff --git a/src/ivrinput/ivrinput_manifest.h b/src/ivrinput/ivrinput_manifest.h new file mode 100644 index 0000000..f00494e --- /dev/null +++ b/src/ivrinput/ivrinput_manifest.h @@ -0,0 +1,44 @@ +#pragma once +#include +#include +#include + +namespace input +{ +/*! +Initializes IVRInput to point at the correct manifest. Can't be done in the +constructor body since objects being initialized require this to be set. +*/ +class Manifest +{ +public: + Manifest() + { + QString actionManifestPath = QStandardPaths::locate( + QStandardPaths::AppDataLocation, + QStringLiteral( "action_manifest.json" ) ); + + auto error = vr::VRInput()->SetActionManifestPath( + actionManifestPath.toStdString().c_str() ); + if ( error != vr::EVRInputError::VRInputError_None ) + { + LOG( ERROR ) << "Error setting action manifest path: " + << actionManifestPath << ". OpenVR Error: " << error; + } + } + + // Explicitly deleted because this object should do nothing but run the + // constructor during initialization. + // Destructor left blank on purpose since there are no terminating calls. + ~Manifest() {} + // Copy constructor + Manifest( const Manifest& ) = delete; + // Copy assignment + Manifest& operator=( const Manifest& ) = delete; + // Move constructor + Manifest( const Manifest&& ) = delete; + // Move assignment + Manifest& operator=( const Manifest&& ) = delete; +}; + +} // namespace input diff --git a/src/ivrinput/steamivrinput.cpp b/src/ivrinput/steamivrinput.cpp new file mode 100644 index 0000000..55ba537 --- /dev/null +++ b/src/ivrinput/steamivrinput.cpp @@ -0,0 +1,114 @@ +#include "steamivrinput.h" +#include +#include +#include +#include + +namespace input +{ +vr::InputDigitalActionData_t getDigitalActionData( Action& action ) +{ + if ( action.type() != ActionType::Digital ) + { + LOG( ERROR ) + << "Action was passed to IVRInput getDigitalActionData without " + "being a digital type. Action: " + << action.name(); + + throw std::runtime_error( + "Action was passed to IVRInput getDigitalActionData without being " + "a digital type. See log for details." ); + } + + vr::InputDigitalActionData_t handleData = {}; + + const auto error = vr::VRInput()->GetDigitalActionData( + action.handle(), + &handleData, + sizeof( handleData ), + vr::k_ulInvalidInputValueHandle ); + + if ( error != vr::EVRInputError::VRInputError_None ) + { + LOG( ERROR ) << "Error getting IVRInput Digital Action Data for handle " + << action.name() << ". SteamVR Error: " << error; + } + + return handleData; +} + +vr::InputAnalogActionData_t getAnalogActionData( Action& action ) +{ + if ( action.type() != ActionType::Analog ) + { + LOG( ERROR ) + << "Action was passed to IVRInput getAnalogActionData without " + "being an analog type. Action: " + << action.name(); + + throw std::runtime_error( + "Action was passed to IVRInput getAnalogActionData without being " + "an analog type. See log for details." ); + } + + vr::InputAnalogActionData_t handleData = {}; + + const auto error + = vr::VRInput()->GetAnalogActionData( action.handle(), + &handleData, + sizeof( handleData ), + vr::k_ulInvalidInputValueHandle ); + + if ( error != vr::EVRInputError::VRInputError_None ) + { + LOG( ERROR ) << "Error getting IVRInput Digital Action Data for handle " + << action.name() << ". SteamVR Error: " << error; + } + + return handleData; +} + +bool isDigitalActionActivatedOnce( Action& action ) +{ + const auto handleData = getDigitalActionData( action ); + + return handleData.bState && handleData.bChanged; +} + +bool isDigitalActionActivatedConstant( Action& action ) +{ + const auto handleData = getDigitalActionData( action ); + + return handleData.bState; +} + +SteamIVRInput::SteamIVRInput() + : m_manifest(), m_mainSet( input_strings::k_setMain ), + m_nextTrack( input_strings::k_actionNextTrack, ActionType::Digital ) +{ + m_activeActionSet.ulActionSet = m_mainSet.handle(); + m_activeActionSet.ulRestrictedToDevice = vr::k_ulInvalidInputValueHandle; + m_activeActionSet.nPriority = 0; +} + +bool SteamIVRInput::nextSong() +{ + return isDigitalActionActivatedOnce( m_nextTrack ); +} + +void SteamIVRInput::Loop() +{ + constexpr auto numberOfSets = 1; + + const auto error = vr::VRInput()->UpdateActionState( + &m_activeActionSet, sizeof( m_activeActionSet ), numberOfSets ); + + if ( error != vr::EVRInputError::VRInputError_None ) + { + LOG( ERROR ) + << "Error during IVRInput action state update. OpenVR Error: " + << error; + } +} + +} // namespace input diff --git a/src/ivrinput/steamivrinput.h b/src/ivrinput/steamivrinput.h new file mode 100644 index 0000000..158a542 --- /dev/null +++ b/src/ivrinput/steamivrinput.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include "ivrinput_action.h" +#include "ivrinput_manifest.h" +#include "ivrinput_action_set.h" + +namespace input +{ +class SteamIVRInput +{ +public: + SteamIVRInput(); + void Loop(); + bool nextSong(); + + // Destructor. There are no terminating calls for the IVRInput API, so it + // is left blank. + ~SteamIVRInput() {} + + // These have been explicitly deleted to make sure there are no attempts at + // copying the class in weird ways. It is not worth defining what should + // happen on copy because it simply shouldn't be done. + // They are declared public because compilers will give wrong error messages + // if they are private. + // Copy constructor + SteamIVRInput( const SteamIVRInput& ) = delete; + // Copy assignment + SteamIVRInput& operator=( const SteamIVRInput& ) = delete; + // Move constructor + SteamIVRInput( SteamIVRInput&& ) = delete; + // Move assignment + SteamIVRInput& operator=( const SteamIVRInput&& ) = delete; + +private: + Manifest m_manifest; + ActionSet m_mainSet; + vr::VRActiveActionSet_t m_activeActionSet = {}; + + Action m_nextTrack; +}; + +namespace input_strings +{ + constexpr auto k_actionNextTrack = "/actions/main/in/PlayNextTrack"; + constexpr auto k_setMain = "/actions/main"; + +} // namespace input_strings + +} // namespace input diff --git a/src/overlaycontroller.cpp b/src/overlaycontroller.cpp index 288fba7..1f94e98 100644 --- a/src/overlaycontroller.cpp +++ b/src/overlaycontroller.cpp @@ -32,7 +32,7 @@ OverlayController::OverlayController( bool desktopMode, bool noSound, QQmlEngine& qmlEngine ) : QObject(), m_desktopMode( desktopMode ), m_noSound( noSound ), - m_openVrInit() + m_openVrInit(), m_actions() { // Despite arguably being OpenVR init code, the call is still here because // the TabController uses this directly. Offering it through OpenVR_Init @@ -118,7 +118,6 @@ OverlayController::OverlayController( bool desktopMode, nullptr, "OpenVR Advanced Settings Overlay", "Is OpenVR running?" ); throw std::runtime_error( std::string( "No Overlay interface" ) ); } - m_actions.Init(); // Init controllers m_steamVRTabController.initStage1(); m_chaperoneTabController.initStage1(); @@ -479,7 +478,7 @@ void OverlayController::OnTimeoutPumpEvents() m_actions.Loop(); - if ( m_actions.nextSongSet() ) + if ( m_actions.nextSong() ) { LOG( INFO ) << "NEXT SONG SET!"; } diff --git a/src/overlaycontroller.h b/src/overlaycontroller.h index 9442b06..cb7beaa 100644 --- a/src/overlaycontroller.h +++ b/src/overlaycontroller.h @@ -39,7 +39,7 @@ #include "tabcontrollers/UtilitiesTabController.h" #include "tabcontrollers/AccessibilityTabController.h" -#include "steamivrinput.h" +#include "ivrinput/steamivrinput.h" // application namespace namespace advsettings @@ -92,7 +92,7 @@ private: public: // I know it's an ugly hack to make them public to enable external // access, but I am too lazy to implement getters. - SteamIVRInput m_actions; + input::SteamIVRInput m_actions; SteamVRTabController m_steamVRTabController; ChaperoneTabController m_chaperoneTabController; MoveCenterTabController m_moveCenterTabController; diff --git a/src/overlaycontroller/openvr_init.cpp b/src/overlaycontroller/openvr_init.cpp index 5cc678c..b63ba1a 100644 --- a/src/overlaycontroller/openvr_init.cpp +++ b/src/overlaycontroller/openvr_init.cpp @@ -70,6 +70,10 @@ OpenVR_Init::OpenVR_Init() { reportVersionError( vr::IVRNotifications_Version ); } + else if ( !vr::VR_IsInterfaceVersionValid( vr::IVRInput_Version ) ) + { + reportVersionError( vr::IVRInput_Version ); + } } OpenVR_Init::~OpenVR_Init() diff --git a/src/steamivrinput.cpp b/src/steamivrinput.cpp deleted file mode 100644 index 69edfda..0000000 --- a/src/steamivrinput.cpp +++ /dev/null @@ -1,103 +0,0 @@ -#include "steamivrinput.h" -#include -#include -#include -#include - -void setActionManifestPath( const QString actionManifestPath ) -{ - auto error = vr::VRInput()->SetActionManifestPath( - actionManifestPath.toStdString().c_str() ); - if ( error != vr::EVRInputError::VRInputError_None ) - { - LOG( ERROR ) << "Error setting action manifest path: " - << actionManifestPath << ". OpenVR Error: " << error; - } -} - -vr::VRActionHandle_t getActionHandle( const char* const action ) -{ - vr::VRActionHandle_t actionHandle = 0; - - auto error = vr::VRInput()->GetActionHandle( action, &actionHandle ); - - if ( error != vr::EVRInputError::VRInputError_None ) - { - LOG( ERROR ) << "Error getting handle for '" << action - << "'. OpenVR Error: " << error; - } - - return actionHandle; -} - -vr::VRActionSetHandle_t getActionSetHandle( const char* const set ) -{ - vr::VRActionSetHandle_t setHandle = 0; - - auto error = vr::VRInput()->GetActionSetHandle( set, &setHandle ); - if ( error != vr::EVRInputError::VRInputError_None ) - { - LOG( ERROR ) << "Error getting handle for '" << set - << "'. OpenVR Error: " << error; - } - - return setHandle; -} - -vr::InputDigitalActionData_t getDigitalActionData( vr::VRActionHandle_t handle ) -{ - vr::InputDigitalActionData_t handleData = {}; - - auto error = vr::VRInput()->GetDigitalActionData( - handle, - &handleData, - sizeof( handleData ), - vr::k_ulInvalidInputValueHandle ); - - if ( error != vr::EVRInputError::VRInputError_None ) - { - LOG( ERROR ) << "Error getting Digital Action Data for handle " - << handle << ". SteamVR Error: " << error; - } - - return handleData; -} - -bool isActionActivatedOnce( vr::VRActionHandle_t handle ) -{ - auto handleData = getDigitalActionData( handle ); - - return handleData.bState && handleData.bChanged; -} - -void SteamIVRInput::Init() -{ - QString actionManifestPath - = QStandardPaths::locate( QStandardPaths::AppDataLocation, - QStringLiteral( "action_manifest.json" ) ); - setActionManifestPath( actionManifestPath ); - - m_nextSongHandler = getActionHandle( input_strings::k_actionNextTrack ); - - m_mainSetHandler = getActionSetHandle( input_strings::k_setMain ); - - m_activeActionSet.ulActionSet = m_mainSetHandler; - m_activeActionSet.ulRestrictedToDevice = vr::k_ulInvalidInputValueHandle; - m_activeActionSet.nPriority = 0; -} - -bool SteamIVRInput::nextSongSet() -{ - return isActionActivatedOnce( m_nextSongHandler ); -} - -void SteamIVRInput::Loop() -{ - auto error = vr::VRInput()->UpdateActionState( - &m_activeActionSet, sizeof( m_activeActionSet ), 1 ); - - if ( error != vr::EVRInputError::VRInputError_None ) - { - LOG( ERROR ) << "Loop error."; - } -} diff --git a/src/steamivrinput.h b/src/steamivrinput.h deleted file mode 100644 index 06f0adc..0000000 --- a/src/steamivrinput.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include -#include "ivrinput/ivrinput_action.h" - -class SteamIVRInput -{ -public: - // SteamIVRInput(); - void Init(); - void Loop(); - bool nextSongSet(); - - // These have been explicitly deleted to make sure there are no attempts at - // copying the class in weird ways. It is not worth defining what should - // happen on copy because it simply shouldn't be done. - // Destructor. There are not terminating calls for the IVRInput API, so it - // is unneeded. - //~SteamIVRInput() = delete; - //// Copy constructor - // SteamIVRInput( const SteamIVRInput& ) = delete; - //// Copy assignment - // SteamIVRInput& operator=( const SteamIVRInput& ) = delete; - //// Move constructor - // SteamIVRInput( SteamIVRInput&& ) = delete; - //// Move assignment - // SteamIVRInput& operator=( const SteamIVRInput&& ) = delete; - -private: - vr::VRActionHandle_t m_nextSongHandler = {}; - vr::VRActionSetHandle_t m_mainSetHandler = {}; - vr::VRActiveActionSet_t m_activeActionSet = {}; - vr::InputDigitalActionData_t m_nextSongData = {}; -}; - -namespace input_strings -{ -constexpr auto k_actionNextTrack = "/actions/main/in/PlayNextTrack"; -constexpr auto k_setMain = "/actions/main"; - -} // namespace input_strings From 01b0070e664ac792895e41ffd2b511f1442184da Mon Sep 17 00:00:00 2001 From: Vasily Date: Sat, 19 Jan 2019 21:32:12 +0100 Subject: [PATCH 06/25] Rename steamivrinput.h/.cpp to ivrinput.h/.cpp The name is shorter and identical to the folder name, emphasizing that this is the main file in this module. --- advancedSettings.pro | 4 ++-- src/ivrinput/{steamivrinput.cpp => ivrinput.cpp} | 2 +- src/ivrinput/{steamivrinput.h => ivrinput.h} | 0 src/overlaycontroller.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename src/ivrinput/{steamivrinput.cpp => ivrinput.cpp} (99%) rename src/ivrinput/{steamivrinput.h => ivrinput.h} (100%) diff --git a/advancedSettings.pro b/advancedSettings.pro index 333b26a..a16d072 100644 --- a/advancedSettings.pro +++ b/advancedSettings.pro @@ -59,7 +59,7 @@ SOURCES += src/main.cpp\ src/tabcontrollers/audiomanager/AudioManagerDummy.cpp \ src/tabcontrollers/keyboardinput/KeyboardInputDummy.cpp \ src/overlaycontroller/openvr_init.cpp \ - src/ivrinput/steamivrinput.cpp + src/ivrinput/ivrinput.cpp HEADERS += src/overlaycontroller.h \ src/tabcontrollers/AudioTabController.h \ @@ -83,7 +83,7 @@ HEADERS += src/overlaycontroller.h \ src/ivrinput/ivrinput_action.h \ src/ivrinput/ivrinput_manifest.h \ src/ivrinput/ivrinput_action_set.h \ - src/ivrinput/steamivrinput.h + src/ivrinput/ivrinput.h win32 { SOURCES += src/tabcontrollers/audiomanager/AudioManagerWindows.cpp \ diff --git a/src/ivrinput/steamivrinput.cpp b/src/ivrinput/ivrinput.cpp similarity index 99% rename from src/ivrinput/steamivrinput.cpp rename to src/ivrinput/ivrinput.cpp index 55ba537..a44fdb0 100644 --- a/src/ivrinput/steamivrinput.cpp +++ b/src/ivrinput/ivrinput.cpp @@ -1,4 +1,4 @@ -#include "steamivrinput.h" +#include "ivrinput.h" #include #include #include diff --git a/src/ivrinput/steamivrinput.h b/src/ivrinput/ivrinput.h similarity index 100% rename from src/ivrinput/steamivrinput.h rename to src/ivrinput/ivrinput.h diff --git a/src/overlaycontroller.h b/src/overlaycontroller.h index cb7beaa..89addf0 100644 --- a/src/overlaycontroller.h +++ b/src/overlaycontroller.h @@ -39,7 +39,7 @@ #include "tabcontrollers/UtilitiesTabController.h" #include "tabcontrollers/AccessibilityTabController.h" -#include "ivrinput/steamivrinput.h" +#include "ivrinput/ivrinput.h" // application namespace namespace advsettings From 0c71c289b9e2ef9809ce8aeb9fef0e4708fe5c42 Mon Sep 17 00:00:00 2001 From: Vasily Date: Sun, 20 Jan 2019 20:13:05 +0100 Subject: [PATCH 07/25] Finalize API and add comments --- src/ivrinput/ivrinput.cpp | 52 +++++++++++++++++++++++++++++- src/ivrinput/ivrinput.h | 33 +++++++++++++++++-- src/ivrinput/ivrinput_action.h | 19 +++++++++++ src/ivrinput/ivrinput_action_set.h | 9 ++++++ src/overlaycontroller.cpp | 2 +- 5 files changed, 111 insertions(+), 4 deletions(-) diff --git a/src/ivrinput/ivrinput.cpp b/src/ivrinput/ivrinput.cpp index a44fdb0..fa5aa9a 100644 --- a/src/ivrinput/ivrinput.cpp +++ b/src/ivrinput/ivrinput.cpp @@ -6,6 +6,17 @@ namespace input { +/*! +Wrapper around the IVRInput GetAnalogActionData with error handling. + +The struct is created and zero initalized in this function instead of being +passed as reference since the size is currently only 28 bytes, and currently +only two bools are used from the entire struct. +MSVC on /O2 will likely completely optimize the function call away but even if +it isn't the struct will still need to be created somewhere, and with move +semantics it almost doesn't matter if the struct is created in the calling +function and passed as reference, or created in this function. +*/ vr::InputDigitalActionData_t getDigitalActionData( Action& action ) { if ( action.type() != ActionType::Digital ) @@ -37,6 +48,17 @@ vr::InputDigitalActionData_t getDigitalActionData( Action& action ) return handleData; } +/*! +Wrapper around the IVRInput GetAnalogActionData with error handling. + +The struct is created and zero initalized in this function instead of being +passed as reference since the size is currently only 28 bytes, and currently +only two bools are used from the entire struct. +MSVC on /O2 will likely completely optimize the function call away but even if +it isn't the struct will still need to be created somewhere, and with move +semantics it almost doesn't matter if the struct is created in the calling +function and passed as reference, or created in this function. +*/ vr::InputAnalogActionData_t getAnalogActionData( Action& action ) { if ( action.type() != ActionType::Analog ) @@ -68,6 +90,12 @@ vr::InputAnalogActionData_t getAnalogActionData( Action& action ) return handleData; } +/*! +Gets the action state of an Action. +This will only return true the first time the button is released. It is +necessary to release the button and the push it again in order for this function +to return true again. +*/ bool isDigitalActionActivatedOnce( Action& action ) { const auto handleData = getDigitalActionData( action ); @@ -75,6 +103,10 @@ bool isDigitalActionActivatedOnce( Action& action ) return handleData.bState && handleData.bChanged; } +/*! +Gets the action state of an Action. +This will continually return true while the button is held down. +*/ bool isDigitalActionActivatedConstant( Action& action ) { const auto handleData = getDigitalActionData( action ); @@ -82,6 +114,14 @@ bool isDigitalActionActivatedConstant( Action& action ) return handleData.bState; } +/*! +Sets up the input systems. +Actions should be added to the initializer list before the body of the +constructor. Actions should be certain that the action name strings are +correctly input, and that the correct type is provided. +Wrong action names may not result in direct error messages, but just features +mystically not working. +*/ SteamIVRInput::SteamIVRInput() : m_manifest(), m_mainSet( input_strings::k_setMain ), m_nextTrack( input_strings::k_actionNextTrack, ActionType::Digital ) @@ -90,13 +130,23 @@ SteamIVRInput::SteamIVRInput() m_activeActionSet.ulRestrictedToDevice = vr::k_ulInvalidInputValueHandle; m_activeActionSet.nPriority = 0; } +/*! +Returns true if the next media track should be played. +Will only return true the first time that the button is pressed. Holding the +button down will result in false until it has been released and pushed again. +*/ bool SteamIVRInput::nextSong() { return isDigitalActionActivatedOnce( m_nextTrack ); } -void SteamIVRInput::Loop() +/*! +Updates the active action set(s). +Should be called every frame, or however often you want the input system to +update state. +*/ +void SteamIVRInput::UpdateStates() { constexpr auto numberOfSets = 1; diff --git a/src/ivrinput/ivrinput.h b/src/ivrinput/ivrinput.h index 158a542..7bd2736 100644 --- a/src/ivrinput/ivrinput.h +++ b/src/ivrinput/ivrinput.h @@ -7,17 +7,40 @@ namespace input { +/*! +Responsible for controller input. + +UpdateStates should be called every frame. + +Binary actions should only have one function, +and they should reflect the expected behaviour. I.e. if a button being pressed +and held should only return true the first time, that logic should be in the +function. + +An action in the IVRInput API is entered in the actions manifest. This is a +.json file that is included with the final binary. +Currently an Action is created with a string and a type. The type determines +which function should be used for getting information from the API. The type in +the actions manifest must match the type in the source code. + +Actions should be added as member objects to SteamIVRInput and initialized in +the constructor. They should then make a small number of accessor functions +available for outside use. The internal structs of the IVRInput API are not +entirely stable, and should not be leaked outside this class. + +The name of the actions manifest must be set in ivrinput_manifest.h. +*/ class SteamIVRInput { public: SteamIVRInput(); - void Loop(); + + void UpdateStates(); bool nextSong(); // Destructor. There are no terminating calls for the IVRInput API, so it // is left blank. ~SteamIVRInput() {} - // These have been explicitly deleted to make sure there are no attempts at // copying the class in weird ways. It is not worth defining what should // happen on copy because it simply shouldn't be done. @@ -34,12 +57,18 @@ public: private: Manifest m_manifest; + ActionSet m_mainSet; vr::VRActiveActionSet_t m_activeActionSet = {}; Action m_nextTrack; }; +/*! +The IVRInput system is _very_ finnicky about strings matching and if they don't +match there are usually no errors logged reflecting that. constexpr auto strings +should be used to refer to all action manifest strings. +*/ namespace input_strings { constexpr auto k_actionNextTrack = "/actions/main/in/PlayNextTrack"; diff --git a/src/ivrinput/ivrinput_action.h b/src/ivrinput/ivrinput_action.h index f3fe439..09ca000 100644 --- a/src/ivrinput/ivrinput_action.h +++ b/src/ivrinput/ivrinput_action.h @@ -4,6 +4,11 @@ namespace input { +/*! +The type of function that should be called to get data for the action. +The IVRInput API does not give explicit warnings when the wrong functions are +called. This enum is an attempt at runtime checking. +*/ enum class ActionType { Undefined, @@ -11,6 +16,10 @@ enum class ActionType Analog, }; +/*! +Represents a single action from the actions manifest. Should be instantiated +with a name similar to that of the one in the actions manifest. +*/ class Action { public: @@ -25,14 +34,24 @@ public: << "'. OpenVR Error: " << error; } } + /*! + An API internal handle that identifies an action. + */ vr::VRActionHandle_t handle() const noexcept { return m_handle; } + /*! + The actions manfiest name of the action. Used for error reporting. + */ std::string name() const noexcept { return m_name; } + /*! + The type of the action. Used for runtime checking that the wrong functions + aren't called. + */ ActionType type() const noexcept { return m_type; diff --git a/src/ivrinput/ivrinput_action_set.h b/src/ivrinput/ivrinput_action_set.h index 66b35a0..70b2e5f 100644 --- a/src/ivrinput/ivrinput_action_set.h +++ b/src/ivrinput/ivrinput_action_set.h @@ -4,6 +4,9 @@ namespace input { +/*! +Represents an action set from the action manifest. +*/ class ActionSet { public: @@ -16,10 +19,16 @@ public: << "'. OpenVR Error: " << error; } } + /*! + The actions manfiest name of the set. Used for error reporting. + */ std::string name() { return m_name; } + /*! + An API internal handle that identifies a set. + */ vr::VRActionSetHandle_t handle() { return m_handle; diff --git a/src/overlaycontroller.cpp b/src/overlaycontroller.cpp index 1f94e98..314b068 100644 --- a/src/overlaycontroller.cpp +++ b/src/overlaycontroller.cpp @@ -476,7 +476,7 @@ void OverlayController::OnTimeoutPumpEvents() if ( !vr::VRSystem() ) return; - m_actions.Loop(); + m_actions.UpdateStates(); if ( m_actions.nextSong() ) { From 706f21ebea96328174388ec7480ee9eb8016d8c0 Mon Sep 17 00:00:00 2001 From: Vasily Date: Sun, 20 Jan 2019 22:05:21 +0100 Subject: [PATCH 08/25] Add ability to change IVRInput bindings, move openvr_init into namespace openvr_init was not in a namespace. This led to it being globally available. This was not intended. The solution to overlays not showing up in the bindings interface was chosen for ease of implementation and relative ease of end user use. The Kinect2VR project are using a solution where there is a button to shutdown background mode and use scene mode temporarily. This did not work for our project for unknown reasons. The IVRInput system would become non responsive and spam error messages when VR was shut down. Implementation is here: https://github.com/sharkyh20/KinectToVR/blob/12aeb8b33ab20eeb588c72c5d5186dd4d87784d7/SFMLProject/inc/GUIHandler.h#L700 The command line flag will be represented as a .bat file for the end user, like desktop mode and restarting the VR server. When Valve solves the issue of overlays not showing up in the bindings menu, the entire function can essentially be deleted. Official issues here: https://github.com/ValveSoftware/openvr/issues/914 https://github.com/ValveSoftware/openvr/issues/926 https://github.com/ValveSoftware/openvr/issues/879 It is also possible to make the app appear by changing the C:\Program Files (x86)\Steam\config\vrappconfig\*.vrappconfig file for the project to have a "last_launch_time" of something greater than zero. From experimentation it seems that there's a cutoff time around 2 weeks before the current time. Setting it to the day before seems to be a good bet. (In unix time). --- src/main.cpp | 12 +++++ src/overlaycontroller.h | 2 +- src/overlaycontroller/openvr_init.cpp | 68 ++++++++++++++++++++++++++- src/overlaycontroller/openvr_init.h | 5 ++ 4 files changed, 84 insertions(+), 3 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 65eb57a..cbc85c6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -46,6 +46,7 @@ constexpr auto kNoSound = "-nosound"; constexpr auto kNoManifest = "-nomanifest"; constexpr auto kInstallManifest = "-installmanifest"; constexpr auto kRemoveManifest = "-removemanifest"; +constexpr auto kEnableBindingsInterface = "-bindingsinterface"; } // namespace argument namespace manifest @@ -357,6 +358,8 @@ int main( int argc, char* argv[] ) argc, argv, argument::kInstallManifest ); const bool removeManifest = argument::CheckCommandLineArgument( argc, argv, argument::kRemoveManifest ); + const bool bindingsInterface = argument::CheckCommandLineArgument( + argc, argv, argument::kEnableBindingsInterface ); // If a command line arg is set, make sure the logs reflect that. LOG_IF( desktopMode, INFO ) << "Desktop mode enabled."; @@ -364,6 +367,15 @@ int main( int argc, char* argv[] ) LOG_IF( noManifest, INFO ) << "vrmanifest disabled."; LOG_IF( installManifest, INFO ) << "Install manifest enabled."; LOG_IF( removeManifest, INFO ) << "Remove manifest enabled."; + LOG_IF( bindingsInterface, INFO ) << "Bindings interface enabled."; + + if ( bindingsInterface ) + { + // This is a temporary solution until Valve fixes overlays not + // showing up in the bindings interface. See function description for + // more info. + openvr_init::openBindingsMenu(); + } // It is important that either install_manifest or remove_manifest are true, // otherwise the handleManifests function will not behave properly. diff --git a/src/overlaycontroller.h b/src/overlaycontroller.h index 89addf0..7f34462 100644 --- a/src/overlaycontroller.h +++ b/src/overlaycontroller.h @@ -88,7 +88,7 @@ private: // OpenVR_Init must be declared before any other class that uses OpenVR // function calls since objects are initialized in order of declaration in // the class. - OpenVR_Init m_openVrInit; + openvr_init::OpenVR_Init m_openVrInit; public: // I know it's an ugly hack to make them public to enable external // access, but I am too lazy to implement getters. diff --git a/src/overlaycontroller/openvr_init.cpp b/src/overlaycontroller/openvr_init.cpp index b63ba1a..558d372 100644 --- a/src/overlaycontroller/openvr_init.cpp +++ b/src/overlaycontroller/openvr_init.cpp @@ -2,10 +2,13 @@ #include #include #include "openvr_init.h" +#include +#include "../ivrinput/ivrinput_manifest.h" -OpenVR_Init::OpenVR_Init() +namespace openvr_init +{ +void initializeProperly() { - // Loading the OpenVR Runtime auto initError = vr::VRInitError_None; vr::VR_Init( &initError, vr::VRApplication_Overlay ); if ( initError != vr::VRInitError_None ) @@ -22,6 +25,66 @@ OpenVR_Init::OpenVR_Init() + std::string( vr::VR_GetVRInitErrorAsEnglishDescription( initError ) ) ); } + else + { + LOG( INFO ) << "OpenVR initialized successfully."; + } +} + +/*! +Initializes OpenVR to be in the bindings menu and then exits. +This is needed because all applications initialized with OpenVR that aren't of +type vr::VRApplication_Scene do not show up the in bindings menu. + +This is obviously an ugly hack and should be done away with as soon as Valve +fixes this issue. + +Official Issues for this: +https://github.com/ValveSoftware/openvr/issues/914 +https://github.com/ValveSoftware/openvr/issues/926 +https://github.com/ValveSoftware/openvr/issues/879 + +You can also make it appear by changing the +C:\Program Files (x86)\Steam\config\vrappconfig\*.vrappconfig file for your +project to have a "last_launch_time" of something greater than zero. +From experimentation it seems that there's a cutoff time around 2 weeks before +the current time. Setting it to the day before seems to be a good bet. (In unix +time). +*/ +[[noreturn]] void openBindingsMenu() +{ + // Prefix the logs with this to visibly document that this isn't how things + // are supposed to be. Hopefully someone reads the logs and comes here after + // Valve has fixed it. + constexpr auto logPrefix = "[HACK]::openBindingsMenu "; + + auto initError = vr::VRInitError_None; + LOG( INFO ) << logPrefix << "Initializing application as scene."; + + vr::VR_Init( &initError, vr::VRApplication_Scene ); + + if ( initError != vr::VRInitError_None ) + { + LOG( ERROR ) << logPrefix << "Error initializing application as scene."; + exit( 2 ); + } + else + { + LOG( INFO ) << logPrefix << "Application initialized as scene."; + } + + input::Manifest manifest; + + // SteamVR just needs a good enough time to start up comfortably and for the + // user to get into the bindings menu. + while ( true ) + { + } +} + +OpenVR_Init::OpenVR_Init() +{ + initializeProperly(); // The function call and error message was the same for all version checks. // Specific error messages are unlikely to be necessary since both the type @@ -80,3 +143,4 @@ OpenVR_Init::~OpenVR_Init() { vr::VR_Shutdown(); } +} // namespace openvr_init diff --git a/src/overlaycontroller/openvr_init.h b/src/overlaycontroller/openvr_init.h index bdec289..d150dcd 100644 --- a/src/overlaycontroller/openvr_init.h +++ b/src/overlaycontroller/openvr_init.h @@ -1,5 +1,9 @@ #pragma once +namespace openvr_init +{ +[[noreturn]] void openBindingsMenu(); + /*! Initializes OpenVR and checks validity of interface versions. Shuts down OpenVR when destroyed. @@ -33,3 +37,4 @@ public: // Move assignment const OpenVR_Init operator=( OpenVR_Init&& ) = delete; }; +} // namespace openvr_init From 5ead96bc98e957c29520461653ea9d8b89d501e7 Mon Sep 17 00:00:00 2001 From: Vasily Date: Sun, 20 Jan 2019 22:16:02 +0100 Subject: [PATCH 09/25] Update installer script to include action manifest --- installer/installer.nsi | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/installer/installer.nsi b/installer/installer.nsi index f3d09b0..aec2452 100644 --- a/installer/installer.nsi +++ b/installer/installer.nsi @@ -109,7 +109,13 @@ Section "Install" SecInstall File "${BASEDIR}\LICENSE-GPL.txt" File "${BASEDIR}\LICENSE-MIT.txt" File "${BASEDIR}\LICENSE-VALVE.txt" + File "${BASEDIR}\*.exe" + ;Any action manifests + File "${BASEDIR}\*.json" + ;And their defaults + File "${BASEDIR}\default_action_manifests\*.json" + File "${THIRDDIR}\openvr\bin\win64\*.dll" File "${BASEDIR}\*.dll" File "${PACKAGEDIR}\*.bat" From 4152333f2010f24369d1d9abfe154fce745a1559 Mon Sep 17 00:00:00 2001 From: Vasily Date: Sun, 20 Jan 2019 22:34:20 +0100 Subject: [PATCH 10/25] Update documentation to reflect new bindings interface --- Readme.md | 23 ++++++++++++++++++++++- src/package_files/bindingsinterface.bat | 1 + 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 src/package_files/bindingsinterface.bat diff --git a/Readme.md b/Readme.md index 010b8f2..008bd58 100644 --- a/Readme.md +++ b/Readme.md @@ -7,6 +7,7 @@ > * [Usage](#usage) > * [Installer](#installer) > * [Standalone](#standalone) + > * [Bindings](#bindings) > * [Command Line Arguments](#command_line_arguments) > * [Logging Config](#logging_config) > * [Preview Builds](#preview_builds) @@ -79,10 +80,28 @@ To install for the first time simply start AdvancedSettings.exe once while Steam To upgrade an existing installation first stop SteamVR and delete the old application folder (or override it). Then start SteamVR and run AdvancedSettings.exe once. + +## Bindings + +Advanced Settings supports the OpenVR IVRInput API. In order to make the bindings interface visible, run the `bindingsinterface.bat` file from the install directory. When SteamVR starts, click the menu -> Settings -> Controller Binding. + +If you just run the application normally it will not show up in the bindings interface, this is an OpenVR bug. + +After you're done configuring your bindings, close SteamVR and start it up again. You are now using your new bindings. + +By default no buttons are bound. + +The following actions exist: + +| Action | Type | Explanation | +| ------------- | ------------- | ------------ | +| PlayNextSong | Binary/Button | The same as using the media keys. Plays the next song. | +| TODO | TODO | TODO | + ## Command Line Arguments -The application (`AdvancedSettings.exe`) can be run with the following five optional arguments: +The application (`AdvancedSettings.exe`) can be run with the following optional arguments: `"-desktop"`: Creates a settings window on the desktop as well as in VR. Running `startdesktopmode.bat` in the install directory has the same effect. @@ -94,6 +113,8 @@ The application (`AdvancedSettings.exe`) can be run with the following five opti `"-removemanifest"`: Force uninstalls the `.vrmanifest`. This should be done every time the application is uninstalled. On Windows it is automatically done by the uninstaller. The program will exit early when this flag is set. +`"-bindingsinterface"`: Makes Advanced Settings show up in the SteamVR bindings interface. This is necessary for binding keys to actions. This is the same as running `bindingsinterface.bat` from the executable directory. + In addition, the application can receive the command line arguments from the [Easylogging++ library](https://github.com/zuhd-org/easyloggingpp#application-arguments). diff --git a/src/package_files/bindingsinterface.bat b/src/package_files/bindingsinterface.bat new file mode 100644 index 0000000..0cd6e03 --- /dev/null +++ b/src/package_files/bindingsinterface.bat @@ -0,0 +1 @@ +start AdvancedSettings.exe -bindingsinterface \ No newline at end of file From 3a62ec89bef4501cc9119e58e04e4698a5ed2da5 Mon Sep 17 00:00:00 2001 From: Vasily Date: Sun, 20 Jan 2019 22:54:30 +0100 Subject: [PATCH 11/25] Add comment to openvr_init.cpp --- src/overlaycontroller/openvr_init.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/overlaycontroller/openvr_init.cpp b/src/overlaycontroller/openvr_init.cpp index 558d372..f7a3f5b 100644 --- a/src/overlaycontroller/openvr_init.cpp +++ b/src/overlaycontroller/openvr_init.cpp @@ -73,6 +73,8 @@ time). LOG( INFO ) << logPrefix << "Application initialized as scene."; } + // This needs to be initialized, otherwise the bindings won't show up in the + // menu. input::Manifest manifest; // SteamVR just needs a good enough time to start up comfortably and for the From 667711246ed0abb5d24dfc724575520cdd38d502 Mon Sep 17 00:00:00 2001 From: Vasily Date: Mon, 21 Jan 2019 21:11:28 +0100 Subject: [PATCH 12/25] Add media key bindings, update docs All bindings are disabled by default. This is to prevent confusing behaviour from users who do not know about the new bindings. Forcing users to manually set up their bindings will also enable them to play around with the SteamVR bindings interface, which allows for much more versatile binding options that our current offerings. All actions are curently in main action set. If the selection of bindings was greatly expanded it could be beneficial to encapsulate the options more. The current code is more intended as a proof of concept until our full binding needs have been uncovered. The processInputBindings function should probably also be split into several functions of related bindings, as the amount of bindings grows. --- Readme.md | 17 +++++--- src/ivrinput/ivrinput.cpp | 40 ++++++++++++++++++- src/ivrinput/ivrinput.h | 13 +++++- src/overlaycontroller.cpp | 32 +++++++++++++-- src/overlaycontroller.h | 4 +- src/package_files/action_manifest.json | 24 +++++++++-- .../vive_defaults.json | 34 ++-------------- 7 files changed, 117 insertions(+), 47 deletions(-) diff --git a/Readme.md b/Readme.md index 008bd58..a5b2af1 100644 --- a/Readme.md +++ b/Readme.md @@ -83,20 +83,25 @@ To upgrade an existing installation first stop SteamVR and delete the old applic ## Bindings -Advanced Settings supports the OpenVR IVRInput API. In order to make the bindings interface visible, run the `bindingsinterface.bat` file from the install directory. When SteamVR starts, click the menu -> Settings -> Controller Binding. +Advanced Settings supports the OpenVR IVRInput API. In order to make the bindings interface visible, run the `bindingsinterface.bat` file from the install directory. After you're done configuring your bindings, close SteamVR and start it up again. You are now using your new bindings. -If you just run the application normally it will not show up in the bindings interface, this is an OpenVR bug. +Alternatively, you can change the `matzman666.AdvancedSettings.vrappconfig` in `C:\Program Files (x86)\Steam\config\vrappconfig` to have a `last_launch_time` close to the current date (in [Unix time](https://en.wikipedia.org/wiki/Unix_time)). If in doubt, try using the value from [this](https://time.is/Unix_time_now) site. Running the application normally will now make it show up in the bindings interface. -After you're done configuring your bindings, close SteamVR and start it up again. You are now using your new bindings. +The above two solutions are workarounds to a bug in the OpenVR API. + +After doing one of the above workarounds, when SteamVR starts click the menu -> Settings -> Controller Binding. By default no buttons are bound. -The following actions exist: +The following actions currently exist: | Action | Type | Explanation | | ------------- | ------------- | ------------ | -| PlayNextSong | Binary/Button | The same as using the media keys. Plays the next song. | -| TODO | TODO | TODO | +| NextSong | Binary/Button | The same as using the media keys. Tells a media player to play the next song.| +| PreviousSong | Binary/Button | The same as using the media keys. Tells a media player to play the previous song. | +| pausePlaySong | Binary/Button | The same as using the media keys. Tells the media player to press play/pause. | +| stopSong | Binary/Button | The same as using the media keys. Tells the media player to stop playback. | + ## Command Line Arguments diff --git a/src/ivrinput/ivrinput.cpp b/src/ivrinput/ivrinput.cpp index fa5aa9a..5e0e792 100644 --- a/src/ivrinput/ivrinput.cpp +++ b/src/ivrinput/ivrinput.cpp @@ -124,7 +124,12 @@ mystically not working. */ SteamIVRInput::SteamIVRInput() : m_manifest(), m_mainSet( input_strings::k_setMain ), - m_nextTrack( input_strings::k_actionNextTrack, ActionType::Digital ) + m_nextTrack( input_strings::k_actionNextTrack, ActionType::Digital ), + m_previousTrack( input_strings::k_actionPreviousTrack, + ActionType::Digital ), + m_pausePlayTrack( input_strings::k_actionPausePlayTrack, + ActionType::Digital ), + m_stopTrack( input_strings::k_actionStopTrack, ActionType::Digital ) { m_activeActionSet.ulActionSet = m_mainSet.handle(); m_activeActionSet.ulRestrictedToDevice = vr::k_ulInvalidInputValueHandle; @@ -141,6 +146,39 @@ bool SteamIVRInput::nextSong() return isDigitalActionActivatedOnce( m_nextTrack ); } +/*! +Returns true if the previous media track should be played. + +Will only return true the first time that the button is pressed. Holding the +button down will result in false until it has been released and pushed again. +*/ +bool SteamIVRInput::previousSong() +{ + return isDigitalActionActivatedOnce( m_previousTrack ); +} + +/*! +Returns true if media playback should be paused/played. + +Will only return true the first time that the button is pressed. Holding the +button down will result in false until it has been released and pushed again. +*/ +bool SteamIVRInput::pausePlaySong() +{ + return isDigitalActionActivatedOnce( m_pausePlayTrack ); +} + +/*! +Returns true if media playback should be stopped. + +Will only return true the first time that the button is pressed. Holding the +button down will result in false until it has been released and pushed again. +*/ +bool SteamIVRInput::stopSong() +{ + return isDigitalActionActivatedOnce( m_stopTrack ); +} + /*! Updates the active action set(s). Should be called every frame, or however often you want the input system to diff --git a/src/ivrinput/ivrinput.h b/src/ivrinput/ivrinput.h index 7bd2736..975a447 100644 --- a/src/ivrinput/ivrinput.h +++ b/src/ivrinput/ivrinput.h @@ -37,6 +37,9 @@ public: void UpdateStates(); bool nextSong(); + bool previousSong(); + bool pausePlaySong(); + bool stopSong(); // Destructor. There are no terminating calls for the IVRInput API, so it // is left blank. @@ -61,7 +64,11 @@ private: ActionSet m_mainSet; vr::VRActiveActionSet_t m_activeActionSet = {}; + // Music player bindings Action m_nextTrack; + Action m_previousTrack; + Action m_pausePlayTrack; + Action m_stopTrack; }; /*! @@ -71,7 +78,11 @@ should be used to refer to all action manifest strings. */ namespace input_strings { - constexpr auto k_actionNextTrack = "/actions/main/in/PlayNextTrack"; + constexpr auto k_actionNextTrack = "/actions/main/in/NextTrack"; + constexpr auto k_actionPreviousTrack = "/actions/main/in/PreviousTrack"; + constexpr auto k_actionPausePlayTrack = "/actions/main/in/PausePlayTrack"; + constexpr auto k_actionStopTrack = "/actions/main/in/StopTrack"; + constexpr auto k_setMain = "/actions/main"; } // namespace input_strings diff --git a/src/overlaycontroller.cpp b/src/overlaycontroller.cpp index 314b068..59f2dd3 100644 --- a/src/overlaycontroller.cpp +++ b/src/overlaycontroller.cpp @@ -471,6 +471,33 @@ QPoint OverlayController::getMousePositionForEvent( vr::VREvent_Mouse_t mouse ) return QPoint( mouse.x, y ); } +/*! +Checks if an action has been activated and dispatches the related action if it +has been. + +This function should probably be split into several functions that are specific +to a binding type as the amount of actions grows. +*/ +void OverlayController::processInputBindings() +{ + if ( m_actions.nextSong() ) + { + m_utilitiesTabController.sendMediaNextSong(); + } + if ( m_actions.previousSong() ) + { + m_utilitiesTabController.sendMediaPreviousSong(); + } + if ( m_actions.pausePlaySong() ) + { + m_utilitiesTabController.sendMediaPausePlay(); + } + if ( m_actions.stopSong() ) + { + m_utilitiesTabController.sendMediaStopSong(); + } +} + void OverlayController::OnTimeoutPumpEvents() { if ( !vr::VRSystem() ) @@ -478,10 +505,7 @@ void OverlayController::OnTimeoutPumpEvents() m_actions.UpdateStates(); - if ( m_actions.nextSong() ) - { - LOG( INFO ) << "NEXT SONG SET!"; - } + processInputBindings(); vr::VREvent_t vrEvent; bool chaperoneDataAlreadyUpdated = false; diff --git a/src/overlaycontroller.h b/src/overlaycontroller.h index 7f34462..06dc94b 100644 --- a/src/overlaycontroller.h +++ b/src/overlaycontroller.h @@ -90,9 +90,10 @@ private: // the class. openvr_init::OpenVR_Init m_openVrInit; + input::SteamIVRInput m_actions; + public: // I know it's an ugly hack to make them public to enable external // access, but I am too lazy to implement getters. - input::SteamIVRInput m_actions; SteamVRTabController m_steamVRTabController; ChaperoneTabController m_chaperoneTabController; MoveCenterTabController m_moveCenterTabController; @@ -106,6 +107,7 @@ public: // I know it's an ugly hack to make them public to enable external private: QPoint getMousePositionForEvent( vr::VREvent_Mouse_t mouse ); + void processInputBindings(); public: OverlayController( bool desktopMode, bool noSound, QQmlEngine& qmlEngine ); diff --git a/src/package_files/action_manifest.json b/src/package_files/action_manifest.json index 0b1a38c..d1e40ff 100644 --- a/src/package_files/action_manifest.json +++ b/src/package_files/action_manifest.json @@ -7,7 +7,22 @@ ], "actions": [ { - "name": "/actions/main/in/PlayNextTrack", + "name": "/actions/main/in/NextTrack", + "requirement": "optional", + "type": "boolean" + }, + { + "name": "/actions/main/in/PreviousTrack", + "requirement": "optional", + "type": "boolean" + }, + { + "name": "/actions/main/in/PausePlayTrack", + "requirement": "optional", + "type": "boolean" + }, + { + "name": "/actions/main/in/StopTrack", "requirement": "optional", "type": "boolean" } @@ -22,9 +37,12 @@ { "language_tag": "en", - "/actions/main" : "My Game Actions", + "/actions/main" : "Advanced Settings Actions", - "/actions/main/in/PlayNextTrack" : "Play Next Track" + "/actions/main/in/NextTrack" : "Play Next Track", + "/actions/main/in/PreviousTrack" : "Play Previous Track", + "/actions/main/in/PausePlayTrack" : "Pause/Play Track", + "/actions/main/in/StopTrack" : "Stop Track" } ] } \ No newline at end of file diff --git a/src/package_files/default_action_manifests/vive_defaults.json b/src/package_files/default_action_manifests/vive_defaults.json index 3e2094c..22e600c 100644 --- a/src/package_files/default_action_manifests/vive_defaults.json +++ b/src/package_files/default_action_manifests/vive_defaults.json @@ -3,40 +3,12 @@ "app_key" : "matzman666.advancedsettings", "bindings" : { "/actions/main" : { - "sources" : [ - { - "inputs" : { - "click" : { - "output" : "/actions/main/in/playnexttrack" - } - }, - "mode" : "button", - "path" : "/user/hand/left/input/application_menu" - }, - { - "inputs" : { - "click" : { - "output" : "/actions/main/in/playnexttrack" - } - }, - "mode" : "button", - "path" : "/user/hand/right/input/application_menu" - }, - { - "inputs" : { - "click" : { - "output" : "/actions/main/in/playnexttrack" - } - }, - "mode" : "button", - "path" : "/user/hand/right/input/grip" - } - ] + "sources" : [] } }, "controller_type" : "vive_controller", - "description" : "", - "name" : "My My Test configuration for Vive Controller", + "description" : "Nothing has been bound by default.", + "name" : "Empty Bindings (default)", "options" : {}, "simulated_actions" : [] } From 1ae434e9877961b97d5d28ab4eb5b0eed1fb33b3 Mon Sep 17 00:00:00 2001 From: Vasily Date: Tue, 22 Jan 2019 18:33:38 +0100 Subject: [PATCH 13/25] Swap recommended ways of getting bindings to show up Running with "-bindingsinterface" does not allow for continually updating the bindings or playing around with them. While changing the unix time in the vrappconfig is slightly harder for casual users, it will also give them a way better user experience. --- Readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index a5b2af1..3c443e6 100644 --- a/Readme.md +++ b/Readme.md @@ -83,9 +83,9 @@ To upgrade an existing installation first stop SteamVR and delete the old applic ## Bindings -Advanced Settings supports the OpenVR IVRInput API. In order to make the bindings interface visible, run the `bindingsinterface.bat` file from the install directory. After you're done configuring your bindings, close SteamVR and start it up again. You are now using your new bindings. +Advanced Settings supports the OpenVR IVRInput API. In order to make the bindings interface visible, change the `matzman666.AdvancedSettings.vrappconfig` in `C:\Program Files (x86)\Steam\config\vrappconfig` to have a `last_launch_time` close to the current date (in [Unix time](https://en.wikipedia.org/wiki/Unix_time)). If in doubt, try using the value from [this](https://time.is/Unix_time_now) site. Running the application normally will now make it show up in the bindings interface. -Alternatively, you can change the `matzman666.AdvancedSettings.vrappconfig` in `C:\Program Files (x86)\Steam\config\vrappconfig` to have a `last_launch_time` close to the current date (in [Unix time](https://en.wikipedia.org/wiki/Unix_time)). If in doubt, try using the value from [this](https://time.is/Unix_time_now) site. Running the application normally will now make it show up in the bindings interface. +Alternatively, if the above doesn't work, run the `bindingsinterface.bat` file from the install directory. After you're done configuring your bindings, close SteamVR and start it up again. You are now using your new bindings. Advanced Settings will not show up in the bindings menu unless you're actively running the application with `bindingsinterface.bat`. Advanced Settings will not work normally while running with `bindingsinterface.bat`. The above two solutions are workarounds to a bug in the OpenVR API. From 7531fc61ea6bc145aab044c496467f74e857e6d8 Mon Sep 17 00:00:00 2001 From: Vasily Date: Tue, 22 Jan 2019 20:36:42 +0100 Subject: [PATCH 14/25] Add browser bindings viewer to readme Collecting information in the readme prevents users from having to Google it. --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 3c443e6..97b63e8 100644 --- a/Readme.md +++ b/Readme.md @@ -89,7 +89,7 @@ Alternatively, if the above doesn't work, run the `bindingsinterface.bat` file f The above two solutions are workarounds to a bug in the OpenVR API. -After doing one of the above workarounds, when SteamVR starts click the menu -> Settings -> Controller Binding. +After doing one of the above workarounds, when SteamVR starts click the menu -> Settings -> Controller Binding. You can also visit [this](127.0.0.1:8998/dashboard/controllerbinding.html) site in your internet browser. It's the same as the built in SteamVR bindings menu. SteamVR must be running for it to work. By default no buttons are bound. From 0a49c7a717bda730e4c03e9591c9c7f327049151 Mon Sep 17 00:00:00 2001 From: Vasily Date: Wed, 23 Jan 2019 18:40:12 +0100 Subject: [PATCH 15/25] Add Alt+Enter button to Utilities page, update Win Alt+Tab func Requested in this reddit post: https://www.reddit.com/r/Vive/comments/agnvux/advanced_settings_280_motion_smoothing_audio_and/ee7muqo/ Use case is toggling full screen mode on applications. The Alt+Tab function has been updated with a constexpr var that reduces duplication in the function calls and makes the keys even more explicit. --- src/res/qml/UtilitiesPage.qml | 19 +++++++--- src/tabcontrollers/KeyboardInput.h | 1 + src/tabcontrollers/UtilitiesTabController.cpp | 5 +++ src/tabcontrollers/UtilitiesTabController.h | 1 + .../keyboardinput/KeyboardInputDummy.cpp | 5 +++ .../keyboardinput/KeyboardInputDummy.h | 1 + .../keyboardinput/KeyboardInputWindows.cpp | 36 ++++++++++++++++--- .../keyboardinput/KeyboardInputWindows.h | 1 + 8 files changed, 59 insertions(+), 10 deletions(-) diff --git a/src/res/qml/UtilitiesPage.qml b/src/res/qml/UtilitiesPage.qml index 351c437..7a10d02 100644 --- a/src/res/qml/UtilitiesPage.qml +++ b/src/res/qml/UtilitiesPage.qml @@ -42,10 +42,11 @@ MyStackViewPage { RowLayout { spacing: 18 + Layout.fillWidth: true MyPushButton { text: "Keyboard Input" - Layout.preferredWidth: 200 + Layout.fillWidth: true onClicked: { OverlayController.showKeyboard("", 601) } @@ -61,7 +62,7 @@ MyStackViewPage { MyPushButton { text: "Enter" - Layout.preferredWidth: 200 + Layout.fillWidth: true onClicked: { UtilitiesTabController.sendKeyboardEnter() } @@ -69,7 +70,7 @@ MyStackViewPage { MyPushButton { text: "Alt-Tab" - Layout.preferredWidth: 200 + Layout.fillWidth: true onClicked: { UtilitiesTabController.sendKeyboardAltTab() } @@ -77,7 +78,7 @@ MyStackViewPage { MyPushButton { text: "Backspace" - Layout.preferredWidth: 200 + Layout.fillWidth: true onClicked: { UtilitiesTabController.sendKeyboardBackspace(1) } @@ -85,11 +86,19 @@ MyStackViewPage { MyPushButton { text: "10x Backspace" - Layout.preferredWidth: 200 + Layout.fillWidth: true onClicked: { UtilitiesTabController.sendKeyboardBackspace(10) } } + + MyPushButton { + text: "Alt-Enter" + Layout.fillWidth: true + onClicked: { + UtilitiesTabController.sendKeyboardAltEnter() + } + } } } } diff --git a/src/tabcontrollers/KeyboardInput.h b/src/tabcontrollers/KeyboardInput.h index cb1226a..8821552 100644 --- a/src/tabcontrollers/KeyboardInput.h +++ b/src/tabcontrollers/KeyboardInput.h @@ -17,6 +17,7 @@ public: virtual void sendKeyboardEnter() = 0; virtual void sendKeyboardBackspace( const int count ) = 0; virtual void sendKeyboardAltTab() = 0; + virtual void sendKeyboardAltEnter() = 0; virtual void sendMediaNextSong() = 0; virtual void sendMediaPreviousSong() = 0; virtual void sendMediaPausePlay() = 0; diff --git a/src/tabcontrollers/UtilitiesTabController.cpp b/src/tabcontrollers/UtilitiesTabController.cpp index 948492e..9fd8246 100644 --- a/src/tabcontrollers/UtilitiesTabController.cpp +++ b/src/tabcontrollers/UtilitiesTabController.cpp @@ -78,6 +78,11 @@ void UtilitiesTabController::sendKeyboardEnter() keyboardInput->sendKeyboardEnter(); } +void UtilitiesTabController::sendKeyboardAltEnter() +{ + keyboardInput->sendKeyboardAltEnter(); +} + void UtilitiesTabController::sendKeyboardAltTab() { keyboardInput->sendKeyboardAltTab(); diff --git a/src/tabcontrollers/UtilitiesTabController.h b/src/tabcontrollers/UtilitiesTabController.h index d73f8e8..bf41f6b 100644 --- a/src/tabcontrollers/UtilitiesTabController.h +++ b/src/tabcontrollers/UtilitiesTabController.h @@ -69,6 +69,7 @@ public slots: void sendKeyboardInput( QString input ); void sendKeyboardEnter(); void sendKeyboardAltTab(); + void sendKeyboardAltEnter(); void sendKeyboardBackspace( const int count ); void sendMediaNextSong(); void sendMediaPreviousSong(); diff --git a/src/tabcontrollers/keyboardinput/KeyboardInputDummy.cpp b/src/tabcontrollers/keyboardinput/KeyboardInputDummy.cpp index f9b78c8..bb3f750 100644 --- a/src/tabcontrollers/keyboardinput/KeyboardInputDummy.cpp +++ b/src/tabcontrollers/keyboardinput/KeyboardInputDummy.cpp @@ -28,6 +28,11 @@ void KeyboardInputDummy::sendKeyboardAltTab() // noop } +void KeyboardInputDummy::sendKeyboardAltEnter() +{ + // noop +} + void KeyboardInputDummy::sendMediaNextSong() { // noop diff --git a/src/tabcontrollers/keyboardinput/KeyboardInputDummy.h b/src/tabcontrollers/keyboardinput/KeyboardInputDummy.h index cf63f1f..f67882e 100644 --- a/src/tabcontrollers/keyboardinput/KeyboardInputDummy.h +++ b/src/tabcontrollers/keyboardinput/KeyboardInputDummy.h @@ -13,6 +13,7 @@ public: virtual void sendKeyboardEnter() override; virtual void sendKeyboardBackspace( const int count ) override; virtual void sendKeyboardAltTab() override; + virtual void sendKeyboardAltEnter() override; virtual void sendMediaNextSong() override; virtual void sendMediaPreviousSong() override; virtual void sendMediaPausePlay() override; diff --git a/src/tabcontrollers/keyboardinput/KeyboardInputWindows.cpp b/src/tabcontrollers/keyboardinput/KeyboardInputWindows.cpp index 8e5a84a..4dd2585 100644 --- a/src/tabcontrollers/keyboardinput/KeyboardInputWindows.cpp +++ b/src/tabcontrollers/keyboardinput/KeyboardInputWindows.cpp @@ -234,11 +234,13 @@ a music player. */ void KeyboardInputWindows::sendKeyboardAltTab() { - // VK_MENU is Alt. - const auto pressAlt = createInputStruct( VK_MENU, KeyStatus::Down ); - const auto pressTab = createInputStruct( VK_TAB, KeyStatus::Down ); - const auto releaseAlt = createInputStruct( VK_MENU, KeyStatus::Up ); - const auto releaseTab = createInputStruct( VK_TAB, KeyStatus::Up ); + constexpr auto altKey = VK_MENU; + constexpr auto tabKey = VK_TAB; + + const auto pressAlt = createInputStruct( altKey, KeyStatus::Down ); + const auto pressTab = createInputStruct( tabKey, KeyStatus::Down ); + const auto releaseAlt = createInputStruct( altKey, KeyStatus::Up ); + const auto releaseTab = createInputStruct( tabKey, KeyStatus::Up ); constexpr auto numberOfActions = 4; @@ -248,6 +250,30 @@ void KeyboardInputWindows::sendKeyboardAltTab() sendKeyboardInputRaw( numberOfActions, actions ); } +/*! +Sends an Alt + Tab combination to Windows. + +Used for toggling full screen on games and various applications that appear on +the desktop. +*/ +void KeyboardInputWindows::sendKeyboardAltEnter() +{ + constexpr auto altKey = VK_MENU; + constexpr auto enterKey = VK_RETURN; + + const auto pressAlt = createInputStruct( altKey, KeyStatus::Down ); + const auto pressEnter = createInputStruct( enterKey, KeyStatus::Down ); + const auto releaseAlt = createInputStruct( altKey, KeyStatus::Up ); + const auto releaseEnter = createInputStruct( enterKey, KeyStatus::Up ); + + constexpr auto numberOfActions = 4; + + INPUT actions[numberOfActions] + = { pressAlt, pressEnter, releaseAlt, releaseEnter }; + + sendKeyboardInputRaw( numberOfActions, actions ); +} + /*! Sends a media key Next Song button to Windows. */ diff --git a/src/tabcontrollers/keyboardinput/KeyboardInputWindows.h b/src/tabcontrollers/keyboardinput/KeyboardInputWindows.h index 12ceb44..cfed120 100644 --- a/src/tabcontrollers/keyboardinput/KeyboardInputWindows.h +++ b/src/tabcontrollers/keyboardinput/KeyboardInputWindows.h @@ -13,6 +13,7 @@ public: virtual void sendKeyboardEnter() override; virtual void sendKeyboardBackspace( const int count ) override; virtual void sendKeyboardAltTab() override; + virtual void sendKeyboardAltEnter() override; virtual void sendMediaNextSong() override; virtual void sendMediaPreviousSong() override; virtual void sendMediaPausePlay() override; From 67b9869e44202d3c96a78637c7a0c90fb8d9c7d1 Mon Sep 17 00:00:00 2001 From: Vasily Date: Wed, 23 Jan 2019 18:50:30 +0100 Subject: [PATCH 16/25] Update UtilitiesPage.png with the Alt+Enter button Additionally, the pause/play button has also been changed since last, but the image wasn't changed. --- docs/screenshots/UtilitiesPage.png | Bin 46356 -> 43489 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/screenshots/UtilitiesPage.png b/docs/screenshots/UtilitiesPage.png index 0cddbae86448164742884bfdc5d99221c0be8aa3..6cbe38f330dcf63ffb39d187c3ac9a29764cd9d1 100644 GIT binary patch literal 43489 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 CXDhz| literal 46356 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 V28k_-C9oF)kQSF0s{|VQ|1XA&PP_mB From cd6403b8ffb1729397f2213a3783f0452eb4a048 Mon Sep 17 00:00:00 2001 From: Vasily Date: Wed, 23 Jan 2019 19:31:03 +0100 Subject: [PATCH 17/25] Update .pro file and add test C++17 statements to main The test statements are making sure C++17 features are enabled on all compilers. They should not be pushed to master as they are. --- advancedSettings.pro | 2 +- src/main.cpp | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/advancedSettings.pro b/advancedSettings.pro index a16d072..e66e72e 100644 --- a/advancedSettings.pro +++ b/advancedSettings.pro @@ -5,7 +5,7 @@ #------------------------------------------------- QT += core gui qml quick multimedia widgets -CONFIG += c++14 +CONFIG += c++1z DEFINES += ELPP_THREAD_SAFE ELPP_QT_LOGGING ELPP_NO_DEFAULT_LOG_FILE diff --git a/src/main.cpp b/src/main.cpp index cbc85c6..70ecddd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -348,6 +348,15 @@ int main( int argc, char* argv[] ) { setUpLogging( argc, argv ); + // Initializers in if statements is a C++17 feature. Should be removed after + // making sure CI works with this. + if ( constexpr int i = 2; i > 10 ) + { + // static_assert with no message is a C++17 feature. Should be removed + // after making sure CI works with this. + static_assert( i < 5 ); + } + const bool desktopMode = argument::CheckCommandLineArgument( argc, argv, argument::kDesktopMode ); const bool noSound From 583a62fb9c6e0ba6e63550761a0dd0f110f8e61e Mon Sep 17 00:00:00 2001 From: Vasily Date: Wed, 23 Jan 2019 19:39:29 +0100 Subject: [PATCH 18/25] Update readme badges to reflect C++17 and Python dependency The badge had not been updated for C++14 when we made the change. It had also not been updated to reflect the dependency on Python during the creation of the build script. --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 97b63e8..19aa499 100644 --- a/Readme.md +++ b/Readme.md @@ -1,4 +1,4 @@ -![language](https://img.shields.io/badge/Language-C%2B%2B11%2C%20QML-green.svg) ![dependencies](https://img.shields.io/badge/Dependencies-OpenVR%2C%20Qt5-green.svg) +![language](https://img.shields.io/badge/Language-C%2B%2B17%2C%20QML-green.svg) ![dependencies](https://img.shields.io/badge/Dependencies-OpenVR%2C%20Qt5%2C%20Python3-green.svg) ![license_gpl3](https://img.shields.io/badge/License-GPL%203.0-green.svg) [![Build status](https://ci.appveyor.com/api/projects/status/8oivf5xws6vsq2x3/branch/master?svg=true)](https://ci.appveyor.com/project/icewind1991/openvr-advancedsettings/branch/master) [![Build Status](https://travis-ci.org/OpenVR-Advanced-Settings/OpenVR-AdvancedSettings.svg?branch=master)](https://travis-ci.org/OpenVR-Advanced-Settings/OpenVR-AdvancedSettings) From 80a28bf7449bdaa206d8e47e85fa123af1cafc17 Mon Sep 17 00:00:00 2001 From: Vasily Date: Wed, 23 Jan 2019 19:57:22 +0100 Subject: [PATCH 19/25] Update readme information The Known Bugs section was removed because that's literally why the issue tracker exists. --- Readme.md | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/Readme.md b/Readme.md index 19aa499..2c14445 100644 --- a/Readme.md +++ b/Readme.md @@ -28,7 +28,6 @@ > * [Building on Windows](#building_on_windows) > * [Building on Linux](#building_on_linux) > * [Notes](#notes) - > * [Known Bugs](#known_bugs) > * [License](#license) @@ -42,7 +41,7 @@ Adds an overlay to the OpenVR dashboard that allows access to advanced settings. # Features - Set supersampling values. -- Enable/disable asynchronous and interleaved reprojection. +- Enable/disable motion smoothing and advanced supersample filtering. - Save supersampling and reprojection settings into profiles. - Change several chaperone settings not accessible via SteamVR settings (to e.g. make it completely invisible). - Chaperone Proximity Warning: Several warning methods can be configured. @@ -54,7 +53,7 @@ Adds an overlay to the OpenVR dashboard that allows access to advanced settings. - Mute/unmute and set volume of audio mirror device. - Mute/unmute and set volume of microphone device. - Implements push-to-talk and allows to configure push-to-talk profiles. -- Desktop mode shows a window on the desktop instead of a vr overlay. +- Desktop mode shows a window on the desktop instead of a VR overlay. - Revive settings support (Requires [Revive 1.0.4+](https://github.com/LibreVR/Revive) for full functionality). - VR Keyboard Input Utilities - Simple Alarm Clock @@ -68,13 +67,12 @@ Adds an overlay to the OpenVR dashboard that allows access to advanced settings. Download the newest installer from the [release section](https://github.com/OpenVR-Advanced-Settings/OpenVR-AdvancedSettings/releases) and execute it. -If you have an older standalone version installed, delete the old application folder first. +If you have an older standalone version installed, the installer will automatically uninstall the old version and install the new version. ## Standalone -I am not currently providing standalone Archives if you want a standalone version you can: -Download the newest standalone archive from the [release section](https://github.com/OpenVR-Advanced-Settings/OpenVR-AdvancedSettings/releases)and unpack the archive. +Download the newest standalone archive from the [release section](https://github.com/OpenVR-Advanced-Settings/OpenVR-AdvancedSettings/releases) and unpack the archive. To install for the first time simply start AdvancedSettings.exe once while SteamVR is running. It will register with SteamVR and automatically starts whenever SteamVR starts (Can be disabled in the settings). @@ -108,7 +106,7 @@ The following actions currently exist: The application (`AdvancedSettings.exe`) can be run with the following optional arguments: -`"-desktop"`: Creates a settings window on the desktop as well as in VR. Running `startdesktopmode.bat` in the install directory has the same effect. +`"-desktop"`: Creates a settings window on the desktop, but not in VR. Running `startdesktopmode.bat` in the install directory has the same effect. `"-nosound"`: Forces sound effects off. @@ -118,7 +116,7 @@ The application (`AdvancedSettings.exe`) can be run with the following optional `"-removemanifest"`: Force uninstalls the `.vrmanifest`. This should be done every time the application is uninstalled. On Windows it is automatically done by the uninstaller. The program will exit early when this flag is set. -`"-bindingsinterface"`: Makes Advanced Settings show up in the SteamVR bindings interface. This is necessary for binding keys to actions. This is the same as running `bindingsinterface.bat` from the executable directory. +`"-bindingsinterface"`: Makes Advanced Settings show up in the SteamVR bindings interface. This is necessary for binding keys to actions. This is the same as running `bindingsinterface.bat` from the executable directory. The program will not exit when this is set. No normal functionality is available except using the bindings interface. Bindings set with this active will be available when this isn't active. In addition, the application can receive the command line arguments from the [Easylogging++ library](https://github.com/zuhd-org/easyloggingpp#application-arguments). @@ -130,7 +128,9 @@ The logging output of the [Easylogging++ library](https://github.com/zuhd-org/ea ## Preview builds -If you want to try latest and greatest unreleased features, you can download latest changes in the alpha branches of the repository. +If you want to try latest and greatest unreleased features, you can download latest from the CI (Continuous Integration) server for [Windows](https://ci.appveyor.com/project/icewind1991/openvr-advancedsettings). The Linux CI does not provide binary artifacts. + +These version are not stable and this should be considered for advanced users only. # Documentation @@ -227,6 +227,8 @@ Allows to temporarily move and rotate the center of the playspace. This allows t ![Revive Page](docs/screenshots/RevivePage.png) +**The Revive page is not currently being maintained. Features may not be stable, performant or actually work.** + The Revive page is only visible when the [Revive overlay](https://github.com/LibreVR/Revive) is installed. In case an installed Revive overlay is not correctly detected you can force the Revive page in the settings. Any changes to controller settings are immediately applied by Revive (Requires Revive 1.0.4+). @@ -312,11 +314,6 @@ If you want to contribute changes running `clang-format` is necessary. More deta - You can modify the shutdown wait time when restarting SteamVR by opening restartvrserver.bat and changing the number behind "timeout /t" (unit is seconds). - -# Known Bugs - -- None yet. - # License From d04cf3286396958558a72aaeb3804def060d35f2 Mon Sep 17 00:00:00 2001 From: Vasily Date: Wed, 23 Jan 2019 20:03:36 +0100 Subject: [PATCH 20/25] Update CI badges, update preview version link Having the CI on their own line prevents the initial line from getting way too long. The preview version link should not show in development alpha branches, but only the relatively tested master. --- Readme.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 2c14445..5588017 100644 --- a/Readme.md +++ b/Readme.md @@ -1,5 +1,6 @@ ![language](https://img.shields.io/badge/Language-C%2B%2B17%2C%20QML-green.svg) ![dependencies](https://img.shields.io/badge/Dependencies-OpenVR%2C%20Qt5%2C%20Python3-green.svg) ![license_gpl3](https://img.shields.io/badge/License-GPL%203.0-green.svg) + [![Build status](https://ci.appveyor.com/api/projects/status/8oivf5xws6vsq2x3/branch/master?svg=true)](https://ci.appveyor.com/project/icewind1991/openvr-advancedsettings/branch/master) [![Build Status](https://travis-ci.org/OpenVR-Advanced-Settings/OpenVR-AdvancedSettings.svg?branch=master)](https://travis-ci.org/OpenVR-Advanced-Settings/OpenVR-AdvancedSettings) @@ -128,7 +129,7 @@ The logging output of the [Easylogging++ library](https://github.com/zuhd-org/ea ## Preview builds -If you want to try latest and greatest unreleased features, you can download latest from the CI (Continuous Integration) server for [Windows](https://ci.appveyor.com/project/icewind1991/openvr-advancedsettings). The Linux CI does not provide binary artifacts. +If you want to try latest and greatest unreleased features, you can download latest from the CI (Continuous Integration) server for [Windows](https://ci.appveyor.com/project/icewind1991/openvr-advancedsettings/branch/master). The Linux CI does not provide binary artifacts. These version are not stable and this should be considered for advanced users only. From 43bf20da45940f3e64513f5ff0b434400752be3f Mon Sep 17 00:00:00 2001 From: Vasily Date: Wed, 23 Jan 2019 20:43:44 +0100 Subject: [PATCH 21/25] Fix msvc and clang-msvc warnings with C++17 The codecvt header has been deprecatd for C++17, but no replacement has been specified in the standard. It is possible to use the Windows specific MultiByteToWideChar() and WideCharToMultiByte() from instead, however the codecvt header still works so there's no reason to change it until a proper replacement has been specified in the standard. It is additionally necessary to manually set the standard for clang-msvc to C++17. --- advancedSettings.pro | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/advancedSettings.pro b/advancedSettings.pro index e66e72e..3b9f7ff 100644 --- a/advancedSettings.pro +++ b/advancedSettings.pro @@ -22,6 +22,10 @@ TEMPLATE = app QMAKE_CXXFLAGS_WARN_ON -= -W3 #C4127 was in a third party file with no way to turn off. QMAKE_CXXFLAGS += /W4 /wd4127 + # The codecvt header being used in AudioManagerWindows.cpp is deprecated by the standard, + # but no suitable replacement has been standardized yet. It is possible to use the Windows + # specific MultiByteToWideChar() and WideCharToMultiByte() from + DEFINES += _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING !*clang-msvc { #win32-clang-msvc is unable to correctly build the first time #with warnings as errors on. There seems to be an issue with the moc_predefs.h @@ -31,6 +35,10 @@ TEMPLATE = app } } +win32-clang-msvc{ + QMAKE_CXXFLAGS += /std:c++17 +} + *g++* { QMAKE_CXXFLAGS += -Werror } From 20341021c50af19a0f0dd7841ca6cbd2df21d0a6 Mon Sep 17 00:00:00 2001 From: Vasily Date: Wed, 23 Jan 2019 21:11:36 +0100 Subject: [PATCH 22/25] Update travis to use clang and g++7 --- .travis.yml | 25 ++++++++++++++++++++----- advancedSettings.pro | 4 ++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1223fc0..dbd7fa9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,24 @@ language: cpp dist: xenial -compiler: - - clang - - gcc + +matrix: + include: + - os: linux + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-7 + env: + - QMAKE_SPEC=linux-g++ + + - os: linux + env: + - QMAKE_SPEC=linux-clang + +before_install: + - python ./build_scripts/run-clang-format.py ./src -r --color always install: - sudo add-apt-repository ppa:kubuntu-ppa/backports -y @@ -10,7 +26,6 @@ install: - sudo apt-get install build-essential qtbase5-dev qttools5-dev qtdeclarative5-dev qtmultimedia5-dev qt5-default qttools5-dev-tools script: - - python ./build_scripts/run-clang-format.py ./src -r --color always - mkdir build && cd build - - qmake .. + - qmake .. -spec $QMAKE_SPEC - make diff --git a/advancedSettings.pro b/advancedSettings.pro index 3b9f7ff..18aecae 100644 --- a/advancedSettings.pro +++ b/advancedSettings.pro @@ -40,7 +40,10 @@ win32-clang-msvc{ } *g++* { + #g++-7 is needed for C++17 features. travis does not supply this by default. + QMAKE_CXX = g++-7 QMAKE_CXXFLAGS += -Werror + QMAKE_CXXFLAGS += -std=c++17 } #Look for anything clang that is not clang-msvc, since it does not @@ -49,6 +52,7 @@ win32-clang-msvc{ QMAKE_CXXFLAGS += -Werror #All includes from the third-party directory will not warn. QMAKE_CXXFLAGS += --system-header-prefix=third-party + QMAKE_CXXFLAGS += -std=c++17 } SOURCES += src/main.cpp\ src/overlaycontroller.cpp \ From c0c824d38061e501d136e872002a9598aa93ba8a Mon Sep 17 00:00:00 2001 From: Vasily Date: Wed, 23 Jan 2019 21:56:44 +0100 Subject: [PATCH 23/25] Remove C++17 flags from g++ and clang The flags are already being applied by QMAKE. --- .travis.yml | 2 +- advancedSettings.pro | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index dbd7fa9..ba22bb5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ matrix: - g++-7 env: - QMAKE_SPEC=linux-g++ - + - os: linux env: - QMAKE_SPEC=linux-clang diff --git a/advancedSettings.pro b/advancedSettings.pro index 18aecae..05c41b6 100644 --- a/advancedSettings.pro +++ b/advancedSettings.pro @@ -36,6 +36,7 @@ TEMPLATE = app } win32-clang-msvc{ + #clang-msvc does not enable the c++17 flag with qmake's c++1z flag. QMAKE_CXXFLAGS += /std:c++17 } @@ -43,7 +44,6 @@ win32-clang-msvc{ #g++-7 is needed for C++17 features. travis does not supply this by default. QMAKE_CXX = g++-7 QMAKE_CXXFLAGS += -Werror - QMAKE_CXXFLAGS += -std=c++17 } #Look for anything clang that is not clang-msvc, since it does not @@ -52,7 +52,6 @@ win32-clang-msvc{ QMAKE_CXXFLAGS += -Werror #All includes from the third-party directory will not warn. QMAKE_CXXFLAGS += --system-header-prefix=third-party - QMAKE_CXXFLAGS += -std=c++17 } SOURCES += src/main.cpp\ src/overlaycontroller.cpp \ From 168d9d38fdfbc5332ecdbc292984c900f6c17120 Mon Sep 17 00:00:00 2001 From: Vasily Date: Wed, 23 Jan 2019 22:33:00 +0100 Subject: [PATCH 24/25] Remove C++17 testing code Featured C++17 features to make sure compilers would be able to handle future C++17 features. --- src/main.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 70ecddd..cbc85c6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -348,15 +348,6 @@ int main( int argc, char* argv[] ) { setUpLogging( argc, argv ); - // Initializers in if statements is a C++17 feature. Should be removed after - // making sure CI works with this. - if ( constexpr int i = 2; i > 10 ) - { - // static_assert with no message is a C++17 feature. Should be removed - // after making sure CI works with this. - static_assert( i < 5 ); - } - const bool desktopMode = argument::CheckCommandLineArgument( argc, argv, argument::kDesktopMode ); const bool noSound From 5f17377329f0bb3cfc97f4c8d5c188677b17c8f9 Mon Sep 17 00:00:00 2001 From: Vasily Date: Wed, 23 Jan 2019 22:44:09 +0100 Subject: [PATCH 25/25] Add explanation to disabled warning in .pro file MSVC does not yet support non experimental ways of selectively disabling warnings for some files. --- advancedSettings.pro | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/advancedSettings.pro b/advancedSettings.pro index 05c41b6..4c17b2a 100644 --- a/advancedSettings.pro +++ b/advancedSettings.pro @@ -20,7 +20,8 @@ TEMPLATE = app #D9025: overriding '/W4' with '/W3' QMAKE_CFLAGS_WARN_ON -= -W3 QMAKE_CXXFLAGS_WARN_ON -= -W3 - #C4127 was in a third party file with no way to turn off. + #C4127 is a warning in qvarlengtharray.h that can not be turned off + #on the current version of MSVC. QMAKE_CXXFLAGS += /W4 /wd4127 # The codecvt header being used in AudioManagerWindows.cpp is deprecated by the standard, # but no suitable replacement has been standardized yet. It is possible to use the Windows