#include "setup.h" #include #include #include #ifdef ENABLE_DEBUG_LOGGING constexpr auto debugLoggingEnabled = true; #else constexpr auto debugLoggingEnabled = false; #endif QtMessageHandler originalMessageHandler = nullptr; static FILE* f; // The default Qt message handler prints to stdout on X11 and to the debugger on // Windows. That is borderline useless for us, therefore we create our own // message handler. void mainQtMessageHandler( QtMsgType type, const QMessageLogContext& context, const QString& msg ) { QString message = qFormatLogMessage( type, context, msg ); fprintf( f, "%s\n", qPrintable( message ) ); fflush( f ); if ( originalMessageHandler ) ( *originalMessageHandler )( type, context, msg ); } namespace argument { CommandLineOptions returnCommandLineParser( const MyQApplication& application ) { QCommandLineParser parser; parser.setApplicationDescription( "Advanced Settings overlay for SteamVR." ); parser.addHelpOption(); parser.addVersionOption(); QCommandLineOption desktopMode( k_desktopMode, k_desktopModeDescription ); parser.addOption( desktopMode ); QCommandLineOption forceNoSound( k_forceNoSound, k_forceNoSoundDescription ); parser.addOption( forceNoSound ); QCommandLineOption forceNoManifest( k_forceNoManifest, k_forceNoManifestDescription ); parser.addOption( forceNoManifest ); QCommandLineOption forceInstallManifest( k_forceInstallManifest, k_forceInstallManifestDescription ); parser.addOption( forceInstallManifest ); QCommandLineOption forceRemoveManifest( k_forceRemoveManifest, k_forceRemoveManifestDescription ); parser.addOption( forceRemoveManifest ); QCommandLineOption resetSettings( k_resetSettings, k_resetSettingsDescription ); parser.addOption( resetSettings ); QCommandLineOption graphicsApi( k_graphicsApi, k_graphicsApiDescription, k_graphicsApiValueName, k_graphicsApiDefault ); parser.addOption( graphicsApi ); QCommandLineOption snapshot( k_snapshot, k_snapshotDescription, k_snapshotValueName ); parser.addOption( snapshot ); parser.process( application ); const bool desktopModeEnabled = parser.isSet( desktopMode ); if ( desktopModeEnabled ) qInfo() << "Desktop mode enabled."; const bool forceNoSoundEnabled = parser.isSet( forceNoSound ); if ( forceNoSoundEnabled ) qInfo() << "Sound effects disabled."; const bool forceNoManifestEnabled = parser.isSet( forceNoManifest ); if ( forceNoManifestEnabled ) qInfo() << "Running without applications manifest."; const bool forceInstallManifestEnabled = parser.isSet( forceInstallManifest ); if ( forceInstallManifestEnabled ) qInfo() << "Forcing install of applications manifest."; const bool forceRemoveManifestEnabled = parser.isSet( forceRemoveManifest ); if ( forceRemoveManifestEnabled ) qInfo() << "Forcing removal of applications manifest."; const bool resetSettingsEnabled = parser.isSet( resetSettings ); if ( resetSettingsEnabled ) qInfo() << "Reset SteamVR Settings."; const QString graphicsApiValue = parser.value( graphicsApi ); qInfo() << "Graphics API:" << graphicsApiValue; const QString snapshotValue = parser.value( snapshot ); if ( !snapshotValue.isEmpty() ) qInfo() << "Overlay snapshots to:" << snapshotValue; const CommandLineOptions commandLineArgs{ desktopModeEnabled, forceNoSoundEnabled, forceNoManifestEnabled, forceInstallManifestEnabled, forceRemoveManifestEnabled, resetSettingsEnabled, graphicsApiValue, snapshotValue }; qInfo() << "Command line arguments processed."; return commandLineArgs; } } // namespace argument namespace manifest { // Enables autostart of OpenVR Advanced Settings. void enableApplicationAutostart() { const auto app_error = vr::VRApplications()->SetApplicationAutoLaunch( application_strings::applicationKey, true ); if ( app_error != vr::VRApplicationError_None ) { throw std::runtime_error( std::string( "Could not set auto start: " ) + std::string( vr::VRApplications()->GetApplicationsErrorNameFromEnum( app_error ) ) ); } } // Installs the application manifest. If a manifest with the same application // key is already installed, nothing will happen. // OpenVR must be initialized before calling this function. void installApplicationManifest( const std::string manifestPath ) { if ( vr::VRApplications()->IsApplicationInstalled( application_strings::applicationKey ) ) { qInfo() << "Manifest Is already Installed for Key " << application_strings::applicationKey; } else { const auto app_error = vr::VRApplications()->AddApplicationManifest( manifestPath.c_str() ); qInfo() << "Manifest Installed at: " << manifestPath; if ( app_error != vr::VRApplicationError_None ) { throw std::runtime_error( std::string( "Could not add application manifest: " ) + std::string( vr::VRApplications()->GetApplicationsErrorNameFromEnum( app_error ) ) ); } } } // Removes the application manifest. void removeApplicationManifest( const std::string manifestPath ) { if ( vr::VRApplications()->IsApplicationInstalled( application_strings::applicationKey ) ) { vr::VRApplications()->RemoveApplicationManifest( manifestPath.c_str() ); qInfo() << "Attempting to Remove Manifest At: " << manifestPath; } if ( vr::VRApplications()->IsApplicationInstalled( application_strings::applicationKey ) ) { qCritical() << "Removal Failed, run Advanced Settings from previous " "install directory OR manually delete it in Steam " "install at Steam\\config\\appconfig.json"; } else { qInfo() << "Manifest Removed Successfully"; } } // Removes and then installs the application manifest. void reinstallApplicationManifest( const std::string manifestPath ) { if ( vr::VRApplications()->IsApplicationInstalled( application_strings::applicationKey ) ) { qInfo() << "Manifest Previously Installed"; // String size was arbitrarily chosen by original author. constexpr auto kStringSize = 1024; char oldApplicationWorkingDir[kStringSize] = { 0 }; auto app_error = vr::VRApplicationError_None; vr::VRApplications()->GetApplicationPropertyString( application_strings::applicationKey, vr::VRApplicationProperty_WorkingDirectory_String, oldApplicationWorkingDir, kStringSize, &app_error ); // Steam lists every VR app the user owns in its generated // steamapps.vrmanifest, installed or not, so whoever owns OVRAS on // Steam has this key registered with a steam:// URL and no working // directory. There is no manifest file to remove then: add ours next // to it (installApplicationManifest() would skip the installed key). if ( app_error == vr::VRApplicationError_PropertyNotSet ) { qInfo() << "Key registered without a manifest file (e.g. by " "Steam), not removing it"; const auto add_error = vr::VRApplications()->AddApplicationManifest( manifestPath.c_str() ); if ( add_error != vr::VRApplicationError_None ) { throw std::runtime_error( "Could not add application manifest: " + std::string( vr::VRApplications()->GetApplicationsErrorNameFromEnum( add_error ) ) ); } qInfo() << "Manifest Installed at: " << manifestPath; return; } if ( app_error != vr::VRApplicationError_None ) { throw std::runtime_error( "Could not find working directory of already " "installed application: " + std::string( vr::VRApplications()->GetApplicationsErrorNameFromEnum( app_error ) ) ); } const auto oldManifestPath = QDir::toNativeSeparators( QDir::cleanPath( QDir( oldApplicationWorkingDir ) .absoluteFilePath( kVRManifestName ) ) ) .toStdString(); removeApplicationManifest( oldManifestPath ); } installApplicationManifest( manifestPath ); } void forceRemoveApplicationManifest() { if ( vr::VRApplications()->IsApplicationInstalled( application_strings::applicationKey ) ) { // String size was arbitrarily chosen by original author. constexpr auto kStringSize = 1024; char oldApplicationWorkingDir[kStringSize] = { 0 }; auto app_error = vr::VRApplicationError_None; vr::VRApplications()->GetApplicationPropertyString( application_strings::applicationKey, vr::VRApplicationProperty_WorkingDirectory_String, oldApplicationWorkingDir, kStringSize, &app_error ); if ( app_error != vr::VRApplicationError_None ) { throw std::runtime_error( "Could not find working directory of already " "installed application: " + std::string( vr::VRApplications()->GetApplicationsErrorNameFromEnum( app_error ) ) ); } const auto oldManifestPath = QDir::toNativeSeparators( QDir::cleanPath( QDir( oldApplicationWorkingDir ) .absoluteFilePath( kVRManifestName ) ) ) .toStdString(); removeApplicationManifest( oldManifestPath ); } else { qInfo() << "No Manifest Installed"; } } // Logs what SteamVR reports for OVRAS's app key, to show which entry it uses // when several register the key (e.g. Steam's own, see // reinstallApplicationManifest()). void logApplicationEntry() { const auto property = []( vr::EVRApplicationProperty prop ) { constexpr auto kStringSize = 1024; char value[kStringSize] = { 0 }; auto error = vr::VRApplicationError_None; vr::VRApplications()->GetApplicationPropertyString( application_strings::applicationKey, prop, value, kStringSize, &error ); return error == vr::VRApplicationError_None ? QString( value ) : QString( vr::VRApplications()->GetApplicationsErrorNameFromEnum( error ) ); }; qInfo() << "SteamVR entry for" << application_strings::applicationKey << "- source:" << property( vr::VRApplicationProperty_Source_String ) << "launch type:" << property( vr::VRApplicationProperty_LaunchType_String ) << "working dir:" << property( vr::VRApplicationProperty_WorkingDirectory_String ) << "binary:" << property( vr::VRApplicationProperty_BinaryPath_String ) << "URL:" << property( vr::VRApplicationProperty_URL_String ) << "auto-launch:" << vr::VRApplications()->GetApplicationAutoLaunch( application_strings::applicationKey ); } // Initializes OpenVR and calls the relevant manifest functions. // The .vrmanifest is used by SteamVR however the exact functiontionality is not // documented in the official documentation. // The manifest is installed upon using the NSIS installer for windows (this // program is called with "--force-install-manifest" by the installer) and every // time the program is launched without both "--desktop-mode" and // "--force-no-manifest". The OpenVR initialization is necessary for both // removing and installing manifests. [[noreturn]] void handleManifests( const bool installManifest, const bool removeManifest ) { // Connect as Utility app first: it works without a running SteamVR (e.g. // from the Windows installer). The Steam Frame's SteamVR refuses Utility // apps (VRInitError_Init_NotAvailableToUtilityApps); there, connect as // Background app, which needs a running SteamVR and never starts it. // VR_Init is called directly: initializeOpenVR() retries every error with // waits, including the one expected here. vr::EVRInitError initError = vr::VRInitError_None; vr::VR_Init( &initError, vr::VRApplication_Utility ); if ( initError == vr::VRInitError_Init_NotAvailableToUtilityApps ) { qInfo() << "SteamVR refuses Utility apps, connecting as Background " "app."; vr::VR_Init( &initError, vr::VRApplication_Background ); } if ( initError != vr::VRInitError_None ) { qCritical() << "Failed to connect to SteamVR for manifest handling:" << vr::VR_GetVRInitErrorAsEnglishDescription( initError ); exit( ReturnErrorCode::OPENVR_INIT_ERROR ); } int exit_code = ReturnErrorCode::SUCCESS; try { const auto manifestPath = paths::binaryDirectoryFindFile( manifest::kVRManifestName ); if ( !manifestPath.has_value() ) { throw std::runtime_error( "Could not find application manifest." ); } if ( installManifest ) { reinstallApplicationManifest( *manifestPath ); enableApplicationAutostart(); qInfo() << "Manifest reinstalled."; logApplicationEntry(); } else if ( removeManifest ) { forceRemoveApplicationManifest(); qInfo() << "Manifest removed."; } } catch ( std::exception& e ) { exit_code = ReturnErrorCode::GENERAL_FAILURE; qCritical() << e.what(); } vr::VR_Shutdown(); exit( exit_code ); } } // namespace manifest void setUpLogging() { f = fopen( "AdvancedSettings.log", "a" ); qSetMessagePattern("[%{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}] %{time process}: %{message}"); originalMessageHandler = qInstallMessageHandler( mainQtMessageHandler ); // Debug output only in builds with debugSymbolsAndLogs, as on master // (easylogging's Debug level was disabled otherwise). Some qDebug() calls // run on every tick (e.g. "updateAllPulseData done."). The environment // variable QT_LOGGING_RULES="*.debug=true" takes precedence over this and // turns them on again without a rebuild. if constexpr ( !debugLoggingEnabled ) { QLoggingCategory::setFilterRules( "*.debug=false" ); } }