diff --git a/UI/adv-audio-control.cpp b/UI/adv-audio-control.cpp index 2131419da3b75f..ce677cb83bf108 100644 --- a/UI/adv-audio-control.cpp +++ b/UI/adv-audio-control.cpp @@ -125,7 +125,7 @@ OBSAdvAudioCtrl::OBSAdvAudioCtrl(QGridLayout *, obs_source_t *source_) stackedWidget->addWidget(percent); VolumeType volType = (VolumeType)config_get_int( - GetGlobalConfig(), "BasicWindow", "AdvAudioVolumeType"); + App()->GetUserConfig(), "BasicWindow", "AdvAudioVolumeType"); SetVolumeWidget(volType); diff --git a/UI/api-interface.cpp b/UI/api-interface.cpp index 691f507c424933..ee6dae7da87d47 100644 --- a/UI/api-interface.cpp +++ b/UI/api-interface.cpp @@ -16,9 +16,6 @@ template static T GetOBSRef(QListWidgetItem *item) return item->data(static_cast(QtDataRole::OBSRef)).value(); } -void EnumProfiles(function &&cb); -void EnumSceneCollections(function &&cb); - extern volatile bool streaming_active; extern volatile bool recording_active; extern volatile bool recording_paused; @@ -169,19 +166,17 @@ struct OBSStudioAPI : obs_frontend_callbacks { void obs_frontend_get_scene_collections( std::vector &strings) override { - auto addCollection = [&](const char *name, const char *) { - strings.emplace_back(name); - return true; - }; - - EnumSceneCollections(addCollection); + for (auto &[collectionName, collection] : + main->GetSceneCollectionCache()) { + strings.emplace_back(collectionName); + } } char *obs_frontend_get_current_scene_collection(void) override { - const char *cur_name = config_get_string( - App()->GlobalConfig(), "Basic", "SceneCollection"); - return bstrdup(cur_name); + const OBSSceneCollection ¤tCollection = + main->GetCurrentSceneCollection(); + return bstrdup(currentCollection.name.c_str()); } void obs_frontend_set_current_scene_collection( @@ -207,10 +202,9 @@ struct OBSStudioAPI : obs_frontend_callbacks { bool obs_frontend_add_scene_collection(const char *name) override { bool success = false; - QMetaObject::invokeMethod(main, "AddSceneCollection", + QMetaObject::invokeMethod(main, "NewSceneCollection", WaitConnection(), Q_RETURN_ARG(bool, success), - Q_ARG(bool, true), Q_ARG(QString, QT_UTF8(name))); return success; } @@ -218,29 +212,24 @@ struct OBSStudioAPI : obs_frontend_callbacks { void obs_frontend_get_profiles(std::vector &strings) override { - auto addProfile = [&](const char *name, const char *) { - strings.emplace_back(name); - return true; - }; + const OBSProfileCache &profiles = main->GetProfileCache(); - EnumProfiles(addProfile); + for (auto &[profileName, profile] : profiles) { + strings.emplace_back(profileName); + } } char *obs_frontend_get_current_profile(void) override { - const char *name = config_get_string(App()->GlobalConfig(), - "Basic", "Profile"); - return bstrdup(name); + const OBSProfile &profile = main->GetCurrentProfile(); + return bstrdup(profile.name.c_str()); } char *obs_frontend_get_current_profile_path(void) override { - char profilePath[512]; - int ret = GetProfilePath(profilePath, sizeof(profilePath), ""); - if (ret <= 0) - return nullptr; + const OBSProfile &profile = main->GetCurrentProfile(); - return bstrdup(profilePath); + return bstrdup(profile.path.u8string().c_str()); } void obs_frontend_set_current_profile(const char *profile) override @@ -510,12 +499,24 @@ struct OBSStudioAPI : obs_frontend_callbacks { config_t *obs_frontend_get_profile_config(void) override { - return main->basicConfig; + return main->activeConfiguration; } config_t *obs_frontend_get_global_config(void) override { - return App()->GlobalConfig(); + blog(LOG_WARNING, + "DEPRECATION: obs_frontend_get_global_config is deprecated. Read from global or user configuration explicitly instead."); + return App()->GetAppConfig(); + } + + config_t *obs_frontend_get_app_config(void) override + { + return App()->GetAppConfig(); + } + + config_t *obs_frontend_get_user_config(void) override + { + return App()->GetUserConfig(); } void obs_frontend_open_projector(const char *type, int monitor, diff --git a/UI/auth-twitch.cpp b/UI/auth-twitch.cpp index cc391bc292d29b..7ec2bdd753646e 100644 --- a/UI/auth-twitch.cpp +++ b/UI/auth-twitch.cpp @@ -410,7 +410,7 @@ void TwitchAuth::LoadSecondaryUIPanes() stats->setVisible(false); feed->setVisible(false); } else { - uint32_t lastVersion = config_get_int(App()->GlobalConfig(), + uint32_t lastVersion = config_get_int(App()->GetAppConfig(), "General", "LastVersion"); if (lastVersion <= MAKE_SEMANTIC_VERSION(23, 0, 2)) { diff --git a/UI/frontend-plugins/frontend-tools/scripts.cpp b/UI/frontend-plugins/frontend-tools/scripts.cpp index 2906f7e7a50d55..3e2c433f161fd6 100644 --- a/UI/frontend-plugins/frontend-tools/scripts.cpp +++ b/UI/frontend-plugins/frontend-tools/scripts.cpp @@ -113,7 +113,7 @@ ScriptLogWindow::ScriptLogWindow() : QDialog(nullptr) resize(600, 400); - config_t *global_config = obs_frontend_get_global_config(); + config_t *global_config = obs_frontend_get_user_config(); const char *geom = config_get_string(global_config, "ScriptLogWindow", "geometry"); if (geom != nullptr) { @@ -129,7 +129,7 @@ ScriptLogWindow::ScriptLogWindow() : QDialog(nullptr) ScriptLogWindow::~ScriptLogWindow() { - config_t *global_config = obs_frontend_get_global_config(); + config_t *global_config = obs_frontend_get_user_config(); config_set_string(global_config, "ScriptLogWindow", "geometry", saveGeometry().toBase64().constData()); } @@ -189,7 +189,7 @@ ScriptsTool::ScriptsTool() : QDialog(nullptr), ui(new Ui_ScriptsTool) RefreshLists(); #if PYTHON_UI - config_t *config = obs_frontend_get_global_config(); + config_t *config = obs_frontend_get_user_config(); const char *path = config_get_string(config, "Python", "Path" ARCH_NAME); ui->pythonPath->setText(path); @@ -207,16 +207,15 @@ ScriptsTool::ScriptsTool() : QDialog(nullptr), ui(new Ui_ScriptsTool) QSizePolicy::Expanding); ui->propertiesLayout->addWidget(propertiesView); - config_t *global_config = obs_frontend_get_global_config(); - int row = - config_get_int(global_config, "scripts-tool", "prevScriptRow"); + config_t *user_config = obs_frontend_get_user_config(); + int row = config_get_int(user_config, "scripts-tool", "prevScriptRow"); ui->scripts->setCurrentRow(row); } ScriptsTool::~ScriptsTool() { - config_t *global_config = obs_frontend_get_global_config(); - config_set_int(global_config, "scripts-tool", "prevScriptRow", + config_t *user_config = obs_frontend_get_user_config(); + config_set_int(user_config, "scripts-tool", "prevScriptRow", ui->scripts->currentRow()); } @@ -465,7 +464,7 @@ void ScriptsTool::on_pythonPathBrowse_clicked() QByteArray array = newPath.toUtf8(); const char *path = array.constData(); - config_t *config = obs_frontend_get_global_config(); + config_t *config = obs_frontend_get_user_config(); config_set_string(config, "Python", "Path" ARCH_NAME, path); ui->pythonPath->setText(newPath); @@ -685,7 +684,7 @@ extern "C" void InitScripts() obs_module_text("Scripts")); #if PYTHON_UI - config_t *config = obs_frontend_get_global_config(); + config_t *config = obs_frontend_get_user_config(); const char *python_path = config_get_string(config, "Python", "Path" ARCH_NAME); diff --git a/UI/log-viewer.cpp b/UI/log-viewer.cpp index 322781da13d793..fca16551b58fa8 100644 --- a/UI/log-viewer.cpp +++ b/UI/log-viewer.cpp @@ -23,12 +23,12 @@ OBSLogViewer::OBSLogViewer(QWidget *parent) ui->setupUi(this); bool showLogViewerOnStartup = config_get_bool( - App()->GlobalConfig(), "LogViewer", "ShowLogStartup"); + App()->GetUserConfig(), "LogViewer", "ShowLogStartup"); ui->showStartup->setChecked(showLogViewerOnStartup); - const char *geom = config_get_string(App()->GlobalConfig(), "LogViewer", - "geometry"); + const char *geom = config_get_string(App()->GetUserConfig(), + "LogViewer", "geometry"); if (geom != nullptr) { QByteArray ba = QByteArray::fromBase64(QByteArray(geom)); @@ -40,13 +40,13 @@ OBSLogViewer::OBSLogViewer(QWidget *parent) OBSLogViewer::~OBSLogViewer() { - config_set_string(App()->GlobalConfig(), "LogViewer", "geometry", + config_set_string(App()->GetUserConfig(), "LogViewer", "geometry", saveGeometry().toBase64().constData()); } void OBSLogViewer::on_showStartup_clicked(bool checked) { - config_set_bool(App()->GlobalConfig(), "LogViewer", "ShowLogStartup", + config_set_bool(App()->GetUserConfig(), "LogViewer", "ShowLogStartup", checked); } @@ -57,7 +57,7 @@ void OBSLogViewer::InitLog() char logDir[512]; std::string path; - if (GetConfigPath(logDir, sizeof(logDir), "obs-studio/logs")) { + if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/logs")) { path += logDir; path += "/"; path += App()->GetCurrentLog(); @@ -124,7 +124,7 @@ void OBSLogViewer::AddLine(int type, const QString &str) void OBSLogViewer::on_openButton_clicked() { char logDir[512]; - if (GetConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0) + if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0) return; const char *log = App()->GetCurrentLog(); diff --git a/UI/media-controls.cpp b/UI/media-controls.cpp index fddc5cbf73cfa1..0353c01f2b026f 100644 --- a/UI/media-controls.cpp +++ b/UI/media-controls.cpp @@ -67,7 +67,7 @@ MediaControls::MediaControls(QWidget *parent) connect(ui->slider, &AbsoluteSlider::sliderMoved, this, &MediaControls::AbsoluteSliderMoved); - countDownTimer = config_get_bool(App()->GlobalConfig(), "BasicWindow", + countDownTimer = config_get_bool(App()->GetUserConfig(), "BasicWindow", "MediaControlsCountdownTimer"); QAction *restartAction = new QAction(this); @@ -465,7 +465,7 @@ void MediaControls::on_durationLabel_clicked() { countDownTimer = !countDownTimer; - config_set_bool(App()->GlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "MediaControlsCountdownTimer", countDownTimer); if (MediaPaused()) diff --git a/UI/obs-app-theming.cpp b/UI/obs-app-theming.cpp index b219aadd3df4ed..037bf0160fe088 100644 --- a/UI/obs-app-theming.cpp +++ b/UI/obs-app-theming.cpp @@ -409,7 +409,6 @@ static vector ParseThemeVariables(const char *themeData) void OBSApp::FindThemes() { - string themeDir; QStringList filters; filters << "*.obt" // OBS Base Theme @@ -417,18 +416,24 @@ void OBSApp::FindThemes() << "*.oha" // OBS High-contrast Adjustment layer ; - GetDataFilePath("themes/", themeDir); - QDirIterator it(QString::fromStdString(themeDir), filters, QDir::Files); - while (it.hasNext()) { - auto theme = ParseThemeMeta(it.next()); - if (theme && !themes.contains(theme->id)) - themes[theme->id] = std::move(*theme); + { + string themeDir; + GetDataFilePath("themes/", themeDir); + QDirIterator it(QString::fromStdString(themeDir), filters, + QDir::Files); + while (it.hasNext()) { + auto theme = ParseThemeMeta(it.next()); + if (theme && !themes.contains(theme->id)) + themes[theme->id] = std::move(*theme); + } } - themeDir.resize(1024); - if (GetConfigPath(themeDir.data(), themeDir.capacity(), - "obs-studio/themes/") > 0) { - QDirIterator it(QT_UTF8(themeDir.c_str()), filters, + { + const std::string themeDir = + App()->userConfigLocation.u8string() + + "/obs-studio/themes"; + + QDirIterator it(QString::fromStdString(themeDir), filters, QDir::Files); while (it.hasNext()) { @@ -876,7 +881,8 @@ bool OBSApp::SetTheme(const QString &name) filesystem::path debugOut; char configPath[512]; - if (GetConfigPath(configPath, sizeof(configPath), filename.c_str())) { + if (GetAppConfigPath(configPath, sizeof(configPath), + filename.c_str())) { debugOut = absolute(filesystem::u8path(configPath)); filesystem::create_directories(debugOut.parent_path()); } @@ -940,7 +946,7 @@ bool OBSApp::InitTheme() } char userDir[512]; - if (GetConfigPath(userDir, sizeof(userDir), "obs-studio/themes")) { + if (GetAppConfigPath(userDir, sizeof(userDir), "obs-studio/themes")) { auto configSearchDir = filesystem::u8path(userDir); QDir::addSearchPath("theme", absolute(configSearchDir)); } @@ -948,7 +954,7 @@ bool OBSApp::InitTheme() /* Load list of themes and read their metadata */ FindThemes(); - if (config_get_bool(globalConfig, "Appearance", "AutoReload")) { + if (config_get_bool(userConfig, "Appearance", "AutoReload")) { /* Set up Qt file watcher to automatically reload themes */ themeWatcher = new QFileSystemWatcher(this); connect(themeWatcher.get(), &QFileSystemWatcher::fileChanged, @@ -956,19 +962,19 @@ bool OBSApp::InitTheme() } /* Migrate old theme config key */ - if (config_has_user_value(globalConfig, "General", "CurrentTheme3") && - !config_has_user_value(globalConfig, "Appearance", "Theme")) { - const char *old = config_get_string(globalConfig, "General", + if (config_has_user_value(userConfig, "General", "CurrentTheme3") && + !config_has_user_value(userConfig, "Appearance", "Theme")) { + const char *old = config_get_string(userConfig, "General", "CurrentTheme3"); if (themeMigrations.count(old)) { - config_set_string(globalConfig, "Appearance", "Theme", + config_set_string(userConfig, "Appearance", "Theme", themeMigrations[old].c_str()); } } QString themeName = - config_get_string(globalConfig, "Appearance", "Theme"); + config_get_string(userConfig, "Appearance", "Theme"); if (themeName.isEmpty() || !GetTheme(themeName)) { if (!themeName.isEmpty()) { diff --git a/UI/obs-app.cpp b/UI/obs-app.cpp index aea7b0270dc054..5f37267a372dfa 100644 --- a/UI/obs-app.cpp +++ b/UI/obs-app.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -440,99 +441,119 @@ static void do_log(int log_level, const char *msg, va_list args, void *param) bool OBSApp::InitGlobalConfigDefaults() { - config_set_default_uint(globalConfig, "General", "MaxLogs", 10); - config_set_default_int(globalConfig, "General", "InfoIncrement", -1); - config_set_default_string(globalConfig, "General", "ProcessPriority", + config_set_default_uint(appConfig, "General", "MaxLogs", 10); + config_set_default_int(appConfig, "General", "InfoIncrement", -1); + config_set_default_string(appConfig, "General", "ProcessPriority", "Normal"); - config_set_default_bool(globalConfig, "General", "EnableAutoUpdates", + config_set_default_bool(appConfig, "General", "EnableAutoUpdates", true); - config_set_default_bool(globalConfig, "General", "ConfirmOnExit", true); - #if _WIN32 - config_set_default_string(globalConfig, "Video", "Renderer", + config_set_default_string(appConfig, "Video", "Renderer", "Direct3D 11"); #else - config_set_default_string(globalConfig, "Video", "Renderer", "OpenGL"); + config_set_default_string(appConfig, "Video", "Renderer", "OpenGL"); #endif - config_set_default_bool(globalConfig, "BasicWindow", "PreviewEnabled", +#ifdef _WIN32 + config_set_default_bool(appConfig, "Audio", "DisableAudioDucking", + true); + config_set_default_bool(appConfig, "General", "BrowserHWAccel", true); +#endif + +#ifdef __APPLE__ + config_set_default_bool(appConfig, "General", "BrowserHWAccel", true); + config_set_default_bool(appConfig, "Video", "DisableOSXVSync", true); + config_set_default_bool(appConfig, "Video", "ResetOSXVSyncOnExit", true); - config_set_default_bool(globalConfig, "BasicWindow", - "PreviewProgramMode", false); - config_set_default_bool(globalConfig, "BasicWindow", +#endif + + return true; +} + +bool OBSApp::InitGlobalLocationDefaults() +{ + char path[512]; + + int len = GetAppConfigPath(path, sizeof(path), nullptr); + if (len <= 0) { + OBSErrorBox(NULL, "Unable to get global configuration path."); + return false; + } + + config_set_default_string(appConfig, "Locations", "Configuration", + path); + config_set_default_string(appConfig, "Locations", "SceneCollections", + path); + config_set_default_string(appConfig, "Locations", "Profiles", path); + + return true; +} + +void OBSApp::InitUserConfigDefaults() +{ + config_set_default_bool(userConfig, "General", "ConfirmOnExit", true); + + config_set_default_string(userConfig, "General", "HotkeyFocusType", + "NeverDisableHotkeys"); + + config_set_default_bool(userConfig, "BasicWindow", "PreviewEnabled", + true); + config_set_default_bool(userConfig, "BasicWindow", "PreviewProgramMode", + false); + config_set_default_bool(userConfig, "BasicWindow", "SceneDuplicationMode", true); - config_set_default_bool(globalConfig, "BasicWindow", "SwapScenesMode", + config_set_default_bool(userConfig, "BasicWindow", "SwapScenesMode", true); - config_set_default_bool(globalConfig, "BasicWindow", "SnappingEnabled", + config_set_default_bool(userConfig, "BasicWindow", "SnappingEnabled", true); - config_set_default_bool(globalConfig, "BasicWindow", "ScreenSnapping", + config_set_default_bool(userConfig, "BasicWindow", "ScreenSnapping", true); - config_set_default_bool(globalConfig, "BasicWindow", "SourceSnapping", + config_set_default_bool(userConfig, "BasicWindow", "SourceSnapping", true); - config_set_default_bool(globalConfig, "BasicWindow", "CenterSnapping", + config_set_default_bool(userConfig, "BasicWindow", "CenterSnapping", false); - config_set_default_double(globalConfig, "BasicWindow", "SnapDistance", + config_set_default_double(userConfig, "BasicWindow", "SnapDistance", 10.0); - config_set_default_bool(globalConfig, "BasicWindow", + config_set_default_bool(userConfig, "BasicWindow", "SpacingHelpersEnabled", true); - config_set_default_bool(globalConfig, "BasicWindow", + config_set_default_bool(userConfig, "BasicWindow", "RecordWhenStreaming", false); - config_set_default_bool(globalConfig, "BasicWindow", + config_set_default_bool(userConfig, "BasicWindow", "KeepRecordingWhenStreamStops", false); - config_set_default_bool(globalConfig, "BasicWindow", "SysTrayEnabled", + config_set_default_bool(userConfig, "BasicWindow", "SysTrayEnabled", true); - config_set_default_bool(globalConfig, "BasicWindow", - "SysTrayWhenStarted", false); - config_set_default_bool(globalConfig, "BasicWindow", "SaveProjectors", + config_set_default_bool(userConfig, "BasicWindow", "SysTrayWhenStarted", + false); + config_set_default_bool(userConfig, "BasicWindow", "SaveProjectors", false); - config_set_default_bool(globalConfig, "BasicWindow", "ShowTransitions", + config_set_default_bool(userConfig, "BasicWindow", "ShowTransitions", true); - config_set_default_bool(globalConfig, "BasicWindow", + config_set_default_bool(userConfig, "BasicWindow", "ShowListboxToolbars", true); - config_set_default_bool(globalConfig, "BasicWindow", "ShowStatusBar", + config_set_default_bool(userConfig, "BasicWindow", "ShowStatusBar", true); - config_set_default_bool(globalConfig, "BasicWindow", "ShowSourceIcons", + config_set_default_bool(userConfig, "BasicWindow", "ShowSourceIcons", true); - config_set_default_bool(globalConfig, "BasicWindow", + config_set_default_bool(userConfig, "BasicWindow", "ShowContextToolbars", true); - config_set_default_bool(globalConfig, "BasicWindow", "StudioModeLabels", + config_set_default_bool(userConfig, "BasicWindow", "StudioModeLabels", true); - config_set_default_string(globalConfig, "General", "HotkeyFocusType", - "NeverDisableHotkeys"); - - config_set_default_bool(globalConfig, "BasicWindow", - "VerticalVolControl", false); + config_set_default_bool(userConfig, "BasicWindow", "VerticalVolControl", + false); - config_set_default_bool(globalConfig, "BasicWindow", + config_set_default_bool(userConfig, "BasicWindow", "MultiviewMouseSwitch", true); - config_set_default_bool(globalConfig, "BasicWindow", - "MultiviewDrawNames", true); - - config_set_default_bool(globalConfig, "BasicWindow", - "MultiviewDrawAreas", true); - -#ifdef _WIN32 - config_set_default_bool(globalConfig, "Audio", "DisableAudioDucking", + config_set_default_bool(userConfig, "BasicWindow", "MultiviewDrawNames", true); - config_set_default_bool(globalConfig, "General", "BrowserHWAccel", - true); -#endif -#ifdef __APPLE__ - config_set_default_bool(globalConfig, "General", "BrowserHWAccel", + config_set_default_bool(userConfig, "BasicWindow", "MultiviewDrawAreas", true); - config_set_default_bool(globalConfig, "Video", "DisableOSXVSync", true); - config_set_default_bool(globalConfig, "Video", "ResetOSXVSyncOnExit", - true); -#endif - config_set_default_bool(globalConfig, "BasicWindow", + config_set_default_bool(userConfig, "BasicWindow", "MediaControlsCountdownTimer", true); - - return true; } static bool do_mkdir(const char *path) @@ -549,36 +570,38 @@ static bool MakeUserDirs() { char path[512]; - if (GetConfigPath(path, sizeof(path), "obs-studio/basic") <= 0) + if (GetAppConfigPath(path, sizeof(path), "obs-studio/basic") <= 0) return false; if (!do_mkdir(path)) return false; - if (GetConfigPath(path, sizeof(path), "obs-studio/logs") <= 0) + if (GetAppConfigPath(path, sizeof(path), "obs-studio/logs") <= 0) return false; if (!do_mkdir(path)) return false; - if (GetConfigPath(path, sizeof(path), "obs-studio/profiler_data") <= 0) + if (GetAppConfigPath(path, sizeof(path), "obs-studio/profiler_data") <= + 0) return false; if (!do_mkdir(path)) return false; #ifdef _WIN32 - if (GetConfigPath(path, sizeof(path), "obs-studio/crashes") <= 0) + if (GetAppConfigPath(path, sizeof(path), "obs-studio/crashes") <= 0) return false; if (!do_mkdir(path)) return false; #endif #ifdef WHATSNEW_ENABLED - if (GetConfigPath(path, sizeof(path), "obs-studio/updates") <= 0) + if (GetAppConfigPath(path, sizeof(path), "obs-studio/updates") <= 0) return false; if (!do_mkdir(path)) return false; #endif - if (GetConfigPath(path, sizeof(path), "obs-studio/plugin_config") <= 0) + if (GetAppConfigPath(path, sizeof(path), "obs-studio/plugin_config") <= + 0) return false; if (!do_mkdir(path)) return false; @@ -586,109 +609,41 @@ static bool MakeUserDirs() return true; } -static bool MakeUserProfileDirs() -{ - char path[512]; - - if (GetConfigPath(path, sizeof(path), "obs-studio/basic/profiles") <= 0) - return false; - if (!do_mkdir(path)) - return false; +constexpr std::string_view OBSProfileSubDirectory = "obs-studio/basic/profiles"; +constexpr std::string_view OBSScenesSubDirectory = "obs-studio/basic/scenes"; - if (GetConfigPath(path, sizeof(path), "obs-studio/basic/scenes") <= 0) - return false; - if (!do_mkdir(path)) - return false; - - return true; -} - -static string GetProfileDirFromName(const char *name) +static bool MakeUserProfileDirs() { - string outputPath; - os_glob_t *glob; - char path[512]; - - if (GetConfigPath(path, sizeof(path), "obs-studio/basic/profiles") <= 0) - return outputPath; - - strcat(path, "/*"); - - if (os_glob(path, 0, &glob) != 0) - return outputPath; - - for (size_t i = 0; i < glob->gl_pathc; i++) { - struct os_globent ent = glob->gl_pathv[i]; - if (!ent.directory) - continue; - - strcpy(path, ent.path); - strcat(path, "/basic.ini"); - - ConfigFile config; - if (config.Open(path, CONFIG_OPEN_EXISTING) != 0) - continue; - - const char *curName = - config_get_string(config, "General", "Name"); - if (astrcmpi(curName, name) == 0) { - outputPath = ent.path; - break; + const std::filesystem::path userProfilePath = + App()->userProfilesLocation / + std::filesystem::u8path(OBSProfileSubDirectory); + const std::filesystem::path userScenesPath = + App()->userScenesLocation / + std::filesystem::u8path(OBSScenesSubDirectory); + + if (!std::filesystem::exists(userProfilePath)) { + try { + std::filesystem::create_directories(userProfilePath); + } catch (const std::filesystem::filesystem_error &error) { + blog(LOG_ERROR, + "Failed to create user profile directory '%s'\n%s", + userProfilePath.u8string().c_str(), error.what()); + return false; } } - os_globfree(glob); - - if (!outputPath.empty()) { - replace(outputPath.begin(), outputPath.end(), '\\', '/'); - const char *start = strrchr(outputPath.c_str(), '/'); - if (start) - outputPath.erase(0, start - outputPath.c_str() + 1); - } - - return outputPath; -} - -static string GetSceneCollectionFileFromName(const char *name) -{ - string outputPath; - os_glob_t *glob; - char path[512]; - - if (GetConfigPath(path, sizeof(path), "obs-studio/basic/scenes") <= 0) - return outputPath; - - strcat(path, "/*.json"); - - if (os_glob(path, 0, &glob) != 0) - return outputPath; - - for (size_t i = 0; i < glob->gl_pathc; i++) { - struct os_globent ent = glob->gl_pathv[i]; - if (ent.directory) - continue; - - OBSDataAutoRelease data = - obs_data_create_from_json_file_safe(ent.path, "bak"); - const char *curName = obs_data_get_string(data, "name"); - - if (astrcmpi(name, curName) == 0) { - outputPath = ent.path; - break; + if (!std::filesystem::exists(userScenesPath)) { + try { + std::filesystem::create_directories(userScenesPath); + } catch (const std::filesystem::filesystem_error &error) { + blog(LOG_ERROR, + "Failed to create user scene collection directory '%s'\n%s", + userScenesPath.u8string().c_str(), error.what()); + return false; } } - os_globfree(glob); - - if (!outputPath.empty()) { - outputPath.resize(outputPath.size() - 5); - replace(outputPath.begin(), outputPath.end(), '\\', '/'); - const char *start = strrchr(outputPath.c_str(), '/'); - if (start) - outputPath.erase(0, start - outputPath.c_str() + 1); - } - - return outputPath; + return true; } bool OBSApp::UpdatePre22MultiviewLayout(const char *layout) @@ -698,7 +653,7 @@ bool OBSApp::UpdatePre22MultiviewLayout(const char *layout) if (astrcmpi(layout, "horizontaltop") == 0) { config_set_int( - globalConfig, "BasicWindow", "MultiviewLayout", + userConfig, "BasicWindow", "MultiviewLayout", static_cast( MultiviewLayout::HORIZONTAL_TOP_8_SCENES)); return true; @@ -706,7 +661,7 @@ bool OBSApp::UpdatePre22MultiviewLayout(const char *layout) if (astrcmpi(layout, "horizontalbottom") == 0) { config_set_int( - globalConfig, "BasicWindow", "MultiviewLayout", + userConfig, "BasicWindow", "MultiviewLayout", static_cast( MultiviewLayout::HORIZONTAL_BOTTOM_8_SCENES)); return true; @@ -714,7 +669,7 @@ bool OBSApp::UpdatePre22MultiviewLayout(const char *layout) if (astrcmpi(layout, "verticalleft") == 0) { config_set_int( - globalConfig, "BasicWindow", "MultiviewLayout", + userConfig, "BasicWindow", "MultiviewLayout", static_cast( MultiviewLayout::VERTICAL_LEFT_8_SCENES)); return true; @@ -722,7 +677,7 @@ bool OBSApp::UpdatePre22MultiviewLayout(const char *layout) if (astrcmpi(layout, "verticalright") == 0) { config_set_int( - globalConfig, "BasicWindow", "MultiviewLayout", + userConfig, "BasicWindow", "MultiviewLayout", static_cast( MultiviewLayout::VERTICAL_RIGHT_8_SCENES)); return true; @@ -734,120 +689,212 @@ bool OBSApp::UpdatePre22MultiviewLayout(const char *layout) bool OBSApp::InitGlobalConfig() { char path[512]; - bool changed = false; - int len = GetConfigPath(path, sizeof(path), "obs-studio/global.ini"); + int len = GetAppConfigPath(path, sizeof(path), "obs-studio/global.ini"); if (len <= 0) { return false; } - int errorcode = globalConfig.Open(path, CONFIG_OPEN_ALWAYS); + int errorcode = appConfig.Open(path, CONFIG_OPEN_ALWAYS); if (errorcode != CONFIG_SUCCESS) { OBSErrorBox(NULL, "Failed to open global.ini: %d", errorcode); return false; } + uint32_t lastVersion = + config_get_int(appConfig, "General", "LastVersion"); + + if (lastVersion < MAKE_SEMANTIC_VERSION(31, 0, 0)) { + bool migratedUserSettings = + config_get_bool(appConfig, "General", "Pre31Migrated"); + + if (!migratedUserSettings) { + bool migrated = MigrateGlobalSettings(); + + config_set_bool(appConfig, "General", "Pre31Migrated", + migrated); + config_save_safe(appConfig, "tmp", nullptr); + } + } + + InitGlobalConfigDefaults(); + InitGlobalLocationDefaults(); + + userConfigLocation = std::filesystem::u8path( + config_get_string(appConfig, "Locations", "Configuration")); + userScenesLocation = std::filesystem::u8path( + config_get_string(appConfig, "Locations", "SceneCollections")); + userProfilesLocation = std::filesystem::u8path( + config_get_string(appConfig, "Locations", "Profiles")); + + bool userConfigResult = InitUserConfig(userConfigLocation, lastVersion); + + return userConfigResult; +} + +bool OBSApp::InitUserConfig(std::filesystem::path &userConfigLocation, + uint32_t lastVersion) +{ + bool hasChanges = false; + + const std::string userConfigFile = + userConfigLocation.u8string() + "/obs-studio/user.ini"; + + int errorCode = + userConfig.Open(userConfigFile.c_str(), CONFIG_OPEN_ALWAYS); + + if (errorCode != CONFIG_SUCCESS) { + OBSErrorBox(nullptr, "Failed to open user.ini: %d", errorCode); + return false; + } + + hasChanges = MigrateLegacySettings(lastVersion); + if (!opt_starting_collection.empty()) { - string path = GetSceneCollectionFileFromName( - opt_starting_collection.c_str()); - if (!path.empty()) { - config_set_string(globalConfig, "Basic", + const OBSBasic *basic = + reinterpret_cast(GetMainWindow()); + const std::optional foundCollection = + basic->GetSceneCollectionByName( + opt_starting_collection); + + if (foundCollection) { + config_set_string(userConfig, "Basic", "SceneCollection", - opt_starting_collection.c_str()); - config_set_string(globalConfig, "Basic", - "SceneCollectionFile", path.c_str()); - changed = true; + foundCollection.value().name.c_str()); + config_set_string( + userConfig, "Basic", "SceneCollectionFile", + foundCollection.value().fileName.c_str()); + hasChanges = true; } } if (!opt_starting_profile.empty()) { - string path = - GetProfileDirFromName(opt_starting_profile.c_str()); - if (!path.empty()) { - config_set_string(globalConfig, "Basic", "Profile", - opt_starting_profile.c_str()); - config_set_string(globalConfig, "Basic", "ProfileDir", - path.c_str()); - changed = true; - } - } + const OBSBasic *basic = + reinterpret_cast(GetMainWindow()); - uint32_t lastVersion = - config_get_int(globalConfig, "General", "LastVersion"); + const std::optional foundProfile = + basic->GetProfileByName(opt_starting_profile); - if (!config_has_user_value(globalConfig, "General", "Pre19Defaults")) { - bool useOldDefaults = lastVersion && - lastVersion < - MAKE_SEMANTIC_VERSION(19, 0, 0); + if (foundProfile) { + config_set_string(userConfig, "Basic", "Profile", + foundProfile.value().name.c_str()); + config_set_string( + userConfig, "Basic", "ProfileDir", + foundProfile.value().directoryName.c_str()); - config_set_bool(globalConfig, "General", "Pre19Defaults", - useOldDefaults); - changed = true; + hasChanges = true; + } } - if (!config_has_user_value(globalConfig, "General", "Pre21Defaults")) { - bool useOldDefaults = lastVersion && - lastVersion < - MAKE_SEMANTIC_VERSION(21, 0, 0); - - config_set_bool(globalConfig, "General", "Pre21Defaults", - useOldDefaults); - changed = true; + if (hasChanges) { + config_save_safe(userConfig, "tmp", nullptr); } - if (!config_has_user_value(globalConfig, "General", "Pre23Defaults")) { - bool useOldDefaults = lastVersion && - lastVersion < - MAKE_SEMANTIC_VERSION(23, 0, 0); + InitUserConfigDefaults(); - config_set_bool(globalConfig, "General", "Pre23Defaults", - useOldDefaults); - changed = true; - } + return true; +} + +bool OBSApp::MigrateLegacySettings(const uint32_t lastVersion) +{ + bool hasChanges = false; -#define PRE_24_1_DEFS "Pre24.1Defaults" - if (!config_has_user_value(globalConfig, "General", PRE_24_1_DEFS)) { - bool useOldDefaults = lastVersion && - lastVersion < - MAKE_SEMANTIC_VERSION(24, 1, 0); + const uint32_t v19 = MAKE_SEMANTIC_VERSION(19, 0, 0); + const uint32_t v21 = MAKE_SEMANTIC_VERSION(21, 0, 0); + const uint32_t v23 = MAKE_SEMANTIC_VERSION(23, 0, 0); + const uint32_t v24 = MAKE_SEMANTIC_VERSION(24, 0, 0); + const uint32_t v24_1 = MAKE_SEMANTIC_VERSION(24, 1, 0); - config_set_bool(globalConfig, "General", PRE_24_1_DEFS, - useOldDefaults); - changed = true; + const map defaultsMap{{{v19, "Pre19Defaults"}, + {v21, "Pre21Defaults"}, + {v23, "Pre23Defaults"}, + {v24_1, "Pre24.1Defaults"}}}; + + for (auto &[version, configKey] : defaultsMap) { + if (!config_has_user_value(userConfig, "General", + configKey.c_str())) { + bool useOldDefaults = lastVersion && + lastVersion < version; + config_set_bool(userConfig, "General", + configKey.c_str(), useOldDefaults); + + hasChanges = true; + } } -#undef PRE_24_1_DEFS - if (config_has_user_value(globalConfig, "BasicWindow", + if (config_has_user_value(userConfig, "BasicWindow", "MultiviewLayout")) { const char *layout = config_get_string( - globalConfig, "BasicWindow", "MultiviewLayout"); - changed |= UpdatePre22MultiviewLayout(layout); + userConfig, "BasicWindow", "MultiviewLayout"); + + bool layoutUpdated = UpdatePre22MultiviewLayout(layout); + + hasChanges = hasChanges | layoutUpdated; } - if (lastVersion && lastVersion < MAKE_SEMANTIC_VERSION(24, 0, 0)) { + if (lastVersion && lastVersion < v24) { bool disableHotkeysInFocus = config_get_bool( - globalConfig, "General", "DisableHotkeysInFocus"); - if (disableHotkeysInFocus) - config_set_string(globalConfig, "General", + userConfig, "General", "DisableHotkeysInFocus"); + + if (disableHotkeysInFocus) { + config_set_string(userConfig, "General", "HotkeyFocusType", "DisableHotkeysInFocus"); - changed = true; + } + + hasChanges = true; } - if (changed) - config_save_safe(globalConfig, "tmp", nullptr); + return hasChanges; +} + +static constexpr string_view OBSGlobalIniPath = "/obs-studio/global.ini"; +static constexpr string_view OBSUserIniPath = "/obs-studio/user.ini"; + +bool OBSApp::MigrateGlobalSettings() +{ + char path[512]; + + int len = GetAppConfigPath(path, sizeof(path), nullptr); + if (len <= 0) { + OBSErrorBox(nullptr, + "Unable to get global configuration path."); + return false; + } + + std::string legacyConfigFileString; + legacyConfigFileString.reserve(strlen(path) + OBSGlobalIniPath.size()); + legacyConfigFileString.append(path).append(OBSGlobalIniPath); + + const std::filesystem::path legacyGlobalConfigFile = + std::filesystem::u8path(legacyConfigFileString); - return InitGlobalConfigDefaults(); + std::string configFileString; + configFileString.reserve(strlen(path) + OBSUserIniPath.size()); + configFileString.append(path).append(OBSUserIniPath); + + const std::filesystem::path userConfigFile = + std::filesystem::u8path(configFileString); + + if (std::filesystem::exists(userConfigFile)) { + OBSErrorBox( + nullptr, + "Unable to migrate global configuration - user configuration file already exists."); + return false; + } + + std::filesystem::copy(legacyGlobalConfigFile, userConfigFile); + + return true; } bool OBSApp::InitLocale() { ProfileScope("OBSApp::InitLocale"); - const char *lang = - config_get_string(globalConfig, "General", "Language"); + const char *lang = config_get_string(userConfig, "General", "Language"); bool userLocale = - config_has_user_value(globalConfig, "General", "Language"); + config_has_user_value(userConfig, "General", "Language"); if (!userLocale || !lang || lang[0] == '\0') lang = DEFAULT_LANG; @@ -963,7 +1010,7 @@ bool LoadBranchesFile(vector &out) string branchesText; BPtr branchesFilePath = - GetConfigPathPtr("obs-studio/updates/branches.json"); + GetAppConfigPathPtr("obs-studio/updates/branches.json"); QFile branchesFile(branchesFilePath.Get()); if (!branchesFile.open(QIODevice::ReadOnly)) { @@ -1069,7 +1116,7 @@ OBSApp::~OBSApp() { #ifdef _WIN32 bool disableAudioDucking = - config_get_bool(globalConfig, "Audio", "DisableAudioDucking"); + config_get_bool(userConfig, "Audio", "DisableAudioDucking"); if (disableAudioDucking) DisableAudioDucking(false); #else @@ -1080,9 +1127,9 @@ OBSApp::~OBSApp() #ifdef __APPLE__ bool vsyncDisabled = - config_get_bool(globalConfig, "Video", "DisableOSXVSync"); + config_get_bool(userConfig, "Video", "DisableOSXVSync"); bool resetVSync = - config_get_bool(globalConfig, "Video", "ResetOSXVSyncOnExit"); + config_get_bool(userConfig, "Video", "ResetOSXVSyncOnExit"); if (vsyncDisabled && resetVSync) EnableOSXVSync(true); #endif @@ -1097,82 +1144,124 @@ OBSApp::~OBSApp() static void move_basic_to_profiles(void) { char path[512]; - char new_path[512]; - os_glob_t *glob; - /* if not first time use */ - if (GetConfigPath(path, 512, "obs-studio/basic") <= 0) - return; - if (!os_file_exists(path)) + if (GetAppConfigPath(path, 512, "obs-studio/basic") <= 0) { return; + } - /* if the profiles directory doesn't already exist */ - if (GetConfigPath(new_path, 512, "obs-studio/basic/profiles") <= 0) - return; - if (os_file_exists(new_path)) - return; + const std::filesystem::path basicPath = std::filesystem::u8path(path); - if (os_mkdir(new_path) == MKDIR_ERROR) + if (!std::filesystem::exists(basicPath)) { return; + } - strcat(new_path, "/"); - strcat(new_path, Str("Untitled")); - if (os_mkdir(new_path) == MKDIR_ERROR) - return; + const std::filesystem::path profilesPath = + App()->userProfilesLocation / + std::filesystem::u8path("obs-studio/basic/profiles"); - strcat(path, "/*.*"); - if (os_glob(path, 0, &glob) != 0) + if (std::filesystem::exists(profilesPath)) { return; + } - strcpy(path, new_path); + try { + std::filesystem::create_directories(profilesPath); + } catch (const std::filesystem::filesystem_error &error) { + blog(LOG_ERROR, + "Failed to create profiles directory for migration from basic profile\n%s", + error.what()); + return; + } - for (size_t i = 0; i < glob->gl_pathc; i++) { - struct os_globent ent = glob->gl_pathv[i]; - char *file; + const std::filesystem::path newProfilePath = + profilesPath / std::filesystem::u8path(Str("Untitled")); - if (ent.directory) + for (auto &entry : std::filesystem::directory_iterator(basicPath)) { + if (entry.is_directory()) { continue; + } - file = strrchr(ent.path, '/'); - if (!file++) + if (entry.path().filename().u8string() == "scenes.json") { continue; + } - if (astrcmpi(file, "scenes.json") == 0) - continue; + if (!std::filesystem::exists(newProfilePath)) { + try { + std::filesystem::create_directory( + newProfilePath); + } catch ( + const std::filesystem::filesystem_error &error) { + blog(LOG_ERROR, + "Failed to create profile directory for 'Untitled'\n%s", + error.what()); + return; + } + } - strcpy(new_path, path); - strcat(new_path, "/"); - strcat(new_path, file); - os_rename(ent.path, new_path); - } + const filesystem::path destinationFile = + newProfilePath / entry.path().filename(); + + const auto copyOptions = + std::filesystem::copy_options::overwrite_existing; - os_globfree(glob); + try { + std::filesystem::copy(entry.path(), destinationFile, + copyOptions); + } catch (const std::filesystem::filesystem_error &error) { + blog(LOG_ERROR, + "Failed to copy basic profile file '%s' to new profile 'Untitled'\n%s", + entry.path().filename().u8string().c_str(), + error.what()); + + return; + } + } } static void move_basic_to_scene_collections(void) { char path[512]; - char new_path[512]; - if (GetConfigPath(path, 512, "obs-studio/basic") <= 0) - return; - if (!os_file_exists(path)) + if (GetAppConfigPath(path, 512, "obs-studio/basic") <= 0) { return; + } + + const std::filesystem::path basicPath = std::filesystem::u8path(path); - if (GetConfigPath(new_path, 512, "obs-studio/basic/scenes") <= 0) + if (!std::filesystem::exists(basicPath)) { return; - if (os_file_exists(new_path)) + } + + const std::filesystem::path sceneCollectionPath = + App()->userScenesLocation / + std::filesystem::u8path("obs-studio/basic/scenes"); + + if (std::filesystem::exists(sceneCollectionPath)) { return; + } - if (os_mkdir(new_path) == MKDIR_ERROR) + try { + std::filesystem::create_directories(sceneCollectionPath); + } catch (const std::filesystem::filesystem_error &error) { + blog(LOG_ERROR, + "Failed to create scene collection directory for migration from basic scene collection\n%s", + error.what()); return; + } - strcat(path, "/scenes.json"); - strcat(new_path, "/"); - strcat(new_path, Str("Untitled")); - strcat(new_path, ".json"); + const std::filesystem::path sourceFile = + basicPath / std::filesystem::u8path("scenes.json"); + const std::filesystem::path destinationFile = + (sceneCollectionPath / std::filesystem::u8path(Str("Untitled"))) + .replace_extension(".json"); - os_rename(path, new_path); + try { + std::filesystem::rename(sourceFile, destinationFile); + } catch (const std::filesystem::filesystem_error &error) { + blog(LOG_ERROR, + "Failed to rename basic scene collection file:\n%s", + error.what()); + return; + } } void OBSApp::AppInit() @@ -1188,40 +1277,40 @@ void OBSApp::AppInit() if (!InitTheme()) throw "Failed to load theme"; - config_set_default_string(globalConfig, "Basic", "Profile", + config_set_default_string(userConfig, "Basic", "Profile", Str("Untitled")); - config_set_default_string(globalConfig, "Basic", "ProfileDir", + config_set_default_string(userConfig, "Basic", "ProfileDir", Str("Untitled")); - config_set_default_string(globalConfig, "Basic", "SceneCollection", + config_set_default_string(userConfig, "Basic", "SceneCollection", Str("Untitled")); - config_set_default_string(globalConfig, "Basic", "SceneCollectionFile", + config_set_default_string(userConfig, "Basic", "SceneCollectionFile", Str("Untitled")); - config_set_default_bool(globalConfig, "Basic", "ConfigOnNewProfile", + config_set_default_bool(userConfig, "Basic", "ConfigOnNewProfile", true); - if (!config_has_user_value(globalConfig, "Basic", "Profile")) { - config_set_string(globalConfig, "Basic", "Profile", + if (!config_has_user_value(userConfig, "Basic", "Profile")) { + config_set_string(userConfig, "Basic", "Profile", Str("Untitled")); - config_set_string(globalConfig, "Basic", "ProfileDir", + config_set_string(userConfig, "Basic", "ProfileDir", Str("Untitled")); } - if (!config_has_user_value(globalConfig, "Basic", "SceneCollection")) { - config_set_string(globalConfig, "Basic", "SceneCollection", + if (!config_has_user_value(userConfig, "Basic", "SceneCollection")) { + config_set_string(userConfig, "Basic", "SceneCollection", Str("Untitled")); - config_set_string(globalConfig, "Basic", "SceneCollectionFile", + config_set_string(userConfig, "Basic", "SceneCollectionFile", Str("Untitled")); } #ifdef _WIN32 bool disableAudioDucking = - config_get_bool(globalConfig, "Audio", "DisableAudioDucking"); + config_get_bool(userConfig, "Audio", "DisableAudioDucking"); if (disableAudioDucking) DisableAudioDucking(true); #endif #ifdef __APPLE__ - if (config_get_bool(globalConfig, "Video", "DisableOSXVSync")) + if (config_get_bool(userConfig, "Video", "DisableOSXVSync")) EnableOSXVSync(false); #endif @@ -1237,7 +1326,7 @@ void OBSApp::AppInit() const char *OBSApp::GetRenderModule() const { const char *renderer = - config_get_string(globalConfig, "Video", "Renderer"); + config_get_string(userConfig, "Video", "Renderer"); return (astrcmpi(renderer, "Direct3D 11") == 0) ? DL_D3D11 : DL_OPENGL; } @@ -1246,7 +1335,8 @@ static bool StartupOBS(const char *locale, profiler_name_store_t *store) { char path[512]; - if (GetConfigPath(path, sizeof(path), "obs-studio/plugin_config") <= 0) + if (GetAppConfigPath(path, sizeof(path), "obs-studio/plugin_config") <= + 0) return false; return obs_startup(locale, path, store); @@ -1265,7 +1355,7 @@ void OBSApp::UpdateHotkeyFocusSetting(bool resetState) enableHotkeysOutOfFocus = true; const char *hotkeyFocusType = - config_get_string(globalConfig, "General", "HotkeyFocusType"); + config_get_string(userConfig, "General", "HotkeyFocusType"); if (astrcmpi(hotkeyFocusType, "DisableHotkeysInFocus") == 0) { enableHotkeysInFocus = false; @@ -1341,7 +1431,7 @@ bool OBSApp::OBSInit() #if defined(_WIN32) || defined(__APPLE__) bool browserHWAccel = - config_get_bool(globalConfig, "General", "BrowserHWAccel"); + config_get_bool(userConfig, "General", "BrowserHWAccel"); OBSDataAutoRelease settings = obs_data_create(); obs_data_set_bool(settings, "BrowserHWAccel", browserHWAccel); @@ -1354,7 +1444,7 @@ bool OBSApp::OBSInit() browserHWAccel ? "true" : "false"); #endif #ifdef _WIN32 - bool hideFromCapture = config_get_bool(globalConfig, "BasicWindow", + bool hideFromCapture = config_get_bool(userConfig, "BasicWindow", "HideOBSWindowsFromCapture"); blog(LOG_INFO, "Hide OBS windows from screen capture: %s", hideFromCapture ? "true" : "false"); @@ -1597,13 +1687,13 @@ static uint64_t convert_log_name(bool has_prefix, const char *name) static void delete_oldest_file(bool has_prefix, const char *location) { - BPtr logDir(GetConfigPathPtr(location)); + BPtr logDir(GetAppConfigPathPtr(location)); string oldestLog; uint64_t oldest_ts = (uint64_t)-1; struct os_dirent *entry; unsigned int maxLogs = (unsigned int)config_get_uint( - App()->GlobalConfig(), "General", "MaxLogs"); + App()->GetUserConfig(), "General", "MaxLogs"); os_dir_t *dir = os_opendir(logDir); if (dir) { @@ -1640,7 +1730,7 @@ static void delete_oldest_file(bool has_prefix, const char *location) static void get_last_log(bool has_prefix, const char *subdir_to_use, std::string &last) { - BPtr logDir(GetConfigPathPtr(subdir_to_use)); + BPtr logDir(GetAppConfigPathPtr(subdir_to_use)); struct os_dirent *entry; os_dir_t *dir = os_opendir(logDir); uint64_t highest_ts = 0; @@ -1866,7 +1956,7 @@ static void create_log_file(fstream &logFile) currentLogFile = GenerateTimeDateFilename("txt"); dst << "obs-studio/logs/" << currentLogFile.c_str(); - BPtr path(GetConfigPathPtr(dst.str().c_str())); + BPtr path(GetAppConfigPathPtr(dst.str().c_str())); #ifdef _WIN32 BPtr wpath; @@ -1925,7 +2015,7 @@ static void SaveProfilerData(const ProfilerSnapshot &snap) dst.write(LITERAL_SIZE(".csv.gz")); #undef LITERAL_SIZE - BPtr path = GetConfigPathPtr(dst.str().c_str()); + BPtr path = GetAppConfigPathPtr(dst.str().c_str()); if (!profiler_snapshot_dump_csv_gz(snap.get(), path)) blog(LOG_WARNING, "Could not save profiler data to '%s'", static_cast(path)); @@ -2160,7 +2250,7 @@ static int run_program(fstream &logFile, int argc, char *argv[]) CheckPermission(kScreenCapture); int permissionsDialogLastShown = - config_get_int(GetGlobalConfig(), "General", + config_get_int(App()->GetAppConfig(), "General", "MacOSPermissionsDialogLastShown"); if (permissionsDialogLastShown < MACOS_PERMISSIONS_DIALOG_VERSION) { @@ -2246,7 +2336,7 @@ static void main_crash_handler(const char *format, va_list args, string name = crashFilePath + "/"; name += "Crash " + GenerateTimeDateFilename("txt"); - BPtr path(GetConfigPathPtr(name.c_str())); + BPtr path(GetAppConfigPathPtr(name.c_str())); fstream file; @@ -2350,7 +2440,7 @@ static void load_debug_privilege(void) #define ALLOW_PORTABLE_MODE 0 #endif -int GetConfigPath(char *path, size_t size, const char *name) +int GetAppConfigPath(char *path, size_t size, const char *name) { #if ALLOW_PORTABLE_MODE if (portable_mode) { @@ -2367,7 +2457,7 @@ int GetConfigPath(char *path, size_t size, const char *name) #endif } -char *GetConfigPathPtr(const char *name) +char *GetAppConfigPathPtr(const char *name) { #if ALLOW_PORTABLE_MODE if (portable_mode) { @@ -2455,36 +2545,6 @@ bool GetClosestUnusedFileName(std::string &path, const char *extension) return true; } -bool GetUnusedSceneCollectionFile(std::string &name, std::string &file) -{ - char path[512]; - int ret; - - if (!GetFileSafeName(name.c_str(), file)) { - blog(LOG_WARNING, "Failed to create safe file name for '%s'", - name.c_str()); - return false; - } - - ret = GetConfigPath(path, sizeof(path), "obs-studio/basic/scenes/"); - if (ret <= 0) { - blog(LOG_WARNING, "Failed to get scene collection config path"); - return false; - } - - file.insert(0, path); - - if (!GetClosestUnusedFileName(file, "json")) { - blog(LOG_WARNING, "Failed to get closest file name for %s", - file.c_str()); - return false; - } - - file.erase(file.size() - 5, 5); - file.erase(0, strlen(path)); - return true; -} - bool WindowPositionValid(QRect rect) { for (QScreen *screen : QGuiApplication::screens()) { @@ -2511,7 +2571,7 @@ static void check_safe_mode_sentinel(void) if (disable_shutdown_check) return; - BPtr sentinelPath = GetConfigPathPtr("obs-studio/safe_mode"); + BPtr sentinelPath = GetAppConfigPathPtr("obs-studio/safe_mode"); if (os_file_exists(sentinelPath)) { unclean_shutdown = true; return; @@ -2523,8 +2583,12 @@ static void check_safe_mode_sentinel(void) static void delete_safe_mode_sentinel(void) { - BPtr sentinelPath = GetConfigPathPtr("obs-studio/safe_mode"); +#ifndef NDEBUG + return; +#else + BPtr sentinelPath = GetAppConfigPathPtr("obs-studio/safe_mode"); os_unlink(sentinelPath); +#endif } #ifndef _WIN32 diff --git a/UI/obs-app.hpp b/UI/obs-app.hpp index ee5c24848fb4bf..308bdaeb98e471 100644 --- a/UI/obs-app.hpp +++ b/UI/obs-app.hpp @@ -38,6 +38,7 @@ #include #include #include +#include #include "window-main.hpp" #include "obs-app-theming.hpp" @@ -91,7 +92,8 @@ class OBSApp : public QApplication { private: std::string locale; - ConfigFile globalConfig; + ConfigFile appConfig; + ConfigFile userConfig; TextLookup textLookup; QPointer mainWindow; profiler_name_store_t *profilerNameStore = nullptr; @@ -112,6 +114,15 @@ class OBSApp : public QApplication { bool InitGlobalConfig(); bool InitGlobalConfigDefaults(); + bool InitGlobalLocationDefaults(); + + bool MigrateGlobalSettings(); + bool MigrateLegacySettings(uint32_t lastVersion); + + bool InitUserConfig(std::filesystem::path &userConfigLocation, + uint32_t lastVersion); + void InitUserConfigDefaults(); + bool InitLocale(); bool InitTheme(); @@ -154,7 +165,11 @@ private slots: inline QMainWindow *GetMainWindow() const { return mainWindow.data(); } - inline config_t *GlobalConfig() const { return globalConfig; } + inline config_t *GetAppConfig() const { return appConfig; } + inline config_t *GetUserConfig() const { return userConfig; } + std::filesystem::path userConfigLocation; + std::filesystem::path userScenesLocation; + std::filesystem::path userProfilesLocation; inline const char *GetLocale() const { return locale.c_str(); } @@ -235,8 +250,8 @@ public slots: void StyleChanged(); }; -int GetConfigPath(char *path, size_t size, const char *name); -char *GetConfigPathPtr(const char *name); +int GetAppConfigPath(char *path, size_t size, const char *name); +char *GetAppConfigPathPtr(const char *name); int GetProgramDataPath(char *path, size_t size, const char *name); char *GetProgramDataPathPtr(const char *name); @@ -246,11 +261,6 @@ inline OBSApp *App() return static_cast(qApp); } -inline config_t *GetGlobalConfig() -{ - return App()->GlobalConfig(); -} - std::vector> GetLocaleNames(); inline const char *Str(const char *lookup) { @@ -267,13 +277,6 @@ bool GetUnusedSceneCollectionFile(std::string &name, std::string &file); bool WindowPositionValid(QRect rect); -static inline int GetProfilePath(char *path, size_t size, const char *file) -{ - OBSMainWindow *window = - reinterpret_cast(App()->GetMainWindow()); - return window->GetProfilePath(path, size, file); -} - extern bool portable_mode; extern bool steam; extern bool safe_mode; diff --git a/UI/obs-frontend-api/obs-frontend-api.cpp b/UI/obs-frontend-api/obs-frontend-api.cpp index 7a98d5b0fdbe3c..0e74ee376bb4be 100644 --- a/UI/obs-frontend-api/obs-frontend-api.cpp +++ b/UI/obs-frontend-api/obs-frontend-api.cpp @@ -394,12 +394,24 @@ config_t *obs_frontend_get_profile_config(void) : nullptr; } -config_t *obs_frontend_get_global_config(void) +config_t *obs_frontend_get_app_config(void) +{ + return !!callbacks_valid() ? c->obs_frontend_get_app_config() : nullptr; +} + +config_t *obs_frontend_get_user_config(void) { - return !!callbacks_valid() ? c->obs_frontend_get_global_config() + return !!callbacks_valid() ? c->obs_frontend_get_user_config() : nullptr; } +config_t *obs_frontend_get_global_config(void) +{ + blog(LOG_WARNING, + "DEPRECATION: obs_frontend_get_global_config is deprecated. Read from global or user configuration explicitly instead."); + return !!callbacks_valid() ? c->obs_frontend_get_app_config() : nullptr; +} + void obs_frontend_open_projector(const char *type, int monitor, const char *geometry, const char *name) { diff --git a/UI/obs-frontend-api/obs-frontend-api.h b/UI/obs-frontend-api/obs-frontend-api.h index 063aa4aa184a18..1282b2819674be 100644 --- a/UI/obs-frontend-api/obs-frontend-api.h +++ b/UI/obs-frontend-api/obs-frontend-api.h @@ -208,7 +208,9 @@ EXPORT obs_output_t *obs_frontend_get_recording_output(void); EXPORT obs_output_t *obs_frontend_get_replay_buffer_output(void); EXPORT config_t *obs_frontend_get_profile_config(void); -EXPORT config_t *obs_frontend_get_global_config(void); +OBS_DEPRECATED EXPORT config_t *obs_frontend_get_global_config(void); +EXPORT config_t *obs_frontend_get_app_config(void); +EXPORT config_t *obs_frontend_get_user_config(void); EXPORT void obs_frontend_set_streaming_service(obs_service_t *service); EXPORT obs_service_t *obs_frontend_get_streaming_service(void); diff --git a/UI/obs-frontend-api/obs-frontend-internal.hpp b/UI/obs-frontend-api/obs-frontend-internal.hpp index 1e600619962f15..d5f1cf46d96ca6 100644 --- a/UI/obs-frontend-api/obs-frontend-internal.hpp +++ b/UI/obs-frontend-api/obs-frontend-internal.hpp @@ -86,7 +86,11 @@ struct obs_frontend_callbacks { virtual obs_output_t *obs_frontend_get_replay_buffer_output(void) = 0; virtual config_t *obs_frontend_get_profile_config(void) = 0; - virtual config_t *obs_frontend_get_global_config(void) = 0; + OBS_DEPRECATED virtual config_t * + obs_frontend_get_global_config(void) = 0; + + virtual config_t *obs_frontend_get_app_config(void) = 0; + virtual config_t *obs_frontend_get_user_config(void) = 0; virtual void obs_frontend_open_projector(const char *type, int monitor, const char *geometry, diff --git a/UI/platform-windows.cpp b/UI/platform-windows.cpp index a5a03c575595d0..457d0402ef3a29 100644 --- a/UI/platform-windows.cpp +++ b/UI/platform-windows.cpp @@ -307,7 +307,7 @@ RunOnceMutex CheckIfAlreadyRunning(bool &already_running) char absPath[512]; *path = 0; *absPath = 0; - GetConfigPath(path, sizeof(path), ""); + GetAppConfigPath(path, sizeof(path), ""); os_get_abs_path(path, absPath, sizeof(absPath)); name = "OBSStudioPortable"; name += absPath; diff --git a/UI/update/mac-update.cpp b/UI/update/mac-update.cpp index 7ee0c073174d27..6e8965c3cb2442 100644 --- a/UI/update/mac-update.cpp +++ b/UI/update/mac-update.cpp @@ -18,8 +18,8 @@ static const char *MAC_DEFAULT_BRANCH = "stable"; bool GetBranch(std::string &selectedBranch) { - const char *config_branch = - config_get_string(GetGlobalConfig(), "General", "UpdateBranch"); + const char *config_branch = config_get_string( + App()->GetAppConfig(), "General", "UpdateBranch"); if (!config_branch) return true; @@ -70,8 +70,8 @@ try { * Validate branch selection */ if (!GetBranch(branch)) { - config_set_string(GetGlobalConfig(), "General", "UpdateBranch", - MAC_DEFAULT_BRANCH); + config_set_string(App()->GetAppConfig(), "General", + "UpdateBranch", MAC_DEFAULT_BRANCH); info(QTStr("Updater.BranchNotFound.Title"), QTStr("Updater.BranchNotFound.Text")); } diff --git a/UI/update/shared-update.cpp b/UI/update/shared-update.cpp index 844f857676ce12..cc70406b839c2a 100644 --- a/UI/update/shared-update.cpp +++ b/UI/update/shared-update.cpp @@ -147,8 +147,8 @@ std::string GetProgramGUID() /* NOTE: this is an arbitrary random number that we use to count the * number of unique OBS installations and is not associated with any * kind of identifiable information */ - const char *pguid = - config_get_string(GetGlobalConfig(), "General", "InstallGUID"); + const char *pguid = config_get_string(App()->GetAppConfig(), "General", + "InstallGUID"); std::string guid; if (pguid) guid = pguid; @@ -157,7 +157,7 @@ std::string GetProgramGUID() GenerateGUID(guid); if (!guid.empty()) - config_set_string(GetGlobalConfig(), "General", + config_set_string(App()->GetAppConfig(), "General", "InstallGUID", guid.c_str()); } @@ -220,7 +220,7 @@ bool FetchAndVerifyFile(const char *name, const char *file, const char *url, uint8_t fileHash[BLAKE2_HASH_LENGTH]; bool success; - BPtr filePath = GetConfigPathPtr(file); + BPtr filePath = GetAppConfigPathPtr(file); if (!extraHeaders.empty()) { headers.insert(headers.end(), extraHeaders.begin(), diff --git a/UI/update/win-update.cpp b/UI/update/win-update.cpp index a0263ad829c3bf..b0f19746237062 100644 --- a/UI/update/win-update.cpp +++ b/UI/update/win-update.cpp @@ -112,8 +112,8 @@ try { bool GetBranchAndUrl(string &selectedBranch, string &manifestUrl) { - const char *config_branch = - config_get_string(GetGlobalConfig(), "General", "UpdateBranch"); + const char *config_branch = config_get_string( + App()->GetAppConfig(), "General", "UpdateBranch"); if (!config_branch) return true; @@ -219,8 +219,8 @@ try { * check branch and get manifest url */ if (!GetBranchAndUrl(branch, manifestUrl)) { - config_set_string(GetGlobalConfig(), "General", "UpdateBranch", - WIN_DEFAULT_BRANCH); + config_set_string(App()->GetAppConfig(), "General", + "UpdateBranch", WIN_DEFAULT_BRANCH); info(QTStr("Updater.BranchNotFound.Title"), QTStr("Updater.BranchNotFound.Text")); } @@ -264,7 +264,7 @@ try { * skip this version if set to skip */ const char *skipUpdateVer = config_get_string( - GetGlobalConfig(), "General", "SkipUpdateVersion"); + App()->GetAppConfig(), "General", "SkipUpdateVersion"); if (!manualUpdate && !repairMode && skipUpdateVer && updateVer == skipUpdateVer) return; @@ -288,13 +288,13 @@ try { if (queryResult == OBSUpdate::No) { if (!manualUpdate) { long long t = (long long)time(nullptr); - config_set_int(GetGlobalConfig(), "General", + config_set_int(App()->GetAppConfig(), "General", "LastUpdateCheck", t); } return; } else if (queryResult == OBSUpdate::Skip) { - config_set_string(GetGlobalConfig(), "General", + config_set_string(App()->GetAppConfig(), "General", "SkipUpdateVersion", updateVer.c_str()); return; @@ -314,7 +314,7 @@ try { * execute updater */ BPtr updateFilePath = - GetConfigPathPtr("obs-studio\\updates\\updater.exe"); + GetAppConfigPathPtr("obs-studio\\updates\\updater.exe"); BPtr wUpdateFilePath; size_t size = os_utf8_to_wcs_ptr(updateFilePath, 0, &wUpdateFilePath); @@ -366,8 +366,8 @@ try { /* force OBS to perform another update check immediately after updating * in case of issues with the new version */ - config_set_int(GetGlobalConfig(), "General", "LastUpdateCheck", 0); - config_set_string(GetGlobalConfig(), "General", "SkipUpdateVersion", + config_set_int(App()->GetAppConfig(), "General", "LastUpdateCheck", 0); + config_set_string(App()->GetAppConfig(), "General", "SkipUpdateVersion", "0"); QMetaObject::invokeMethod(App()->GetMainWindow(), "close"); diff --git a/UI/volume-control.cpp b/UI/volume-control.cpp index 0111858ab1671f..25ad2048404bd3 100644 --- a/UI/volume-control.cpp +++ b/UI/volume-control.cpp @@ -61,9 +61,10 @@ static void ShowUnassignedWarning(const char *name) msgbox.exec(); if (cb->isChecked()) { - config_set_bool(App()->GlobalConfig(), "General", + config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutUnassignedSources", true); - config_save_safe(App()->GlobalConfig(), "tmp", nullptr); + config_save_safe(App()->GetUserConfig(), "tmp", + nullptr); } }; @@ -150,7 +151,7 @@ void VolControl::SetMuted(bool) mute->setCheckState(Qt::PartiallyChecked); /* Show notice about the source no being assigned to any tracks */ bool has_shown_warning = - config_get_bool(App()->GlobalConfig(), "General", + config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutUnassignedSources"); if (!has_shown_warning) ShowUnassignedWarning(obs_source_get_name(source)); @@ -459,10 +460,10 @@ void VolumeMeter::setBackgroundNominalColor(QColor c) { p_backgroundNominalColor = std::move(c); - if (config_get_bool(GetGlobalConfig(), "Accessibility", + if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { backgroundNominalColor = color_from_int(config_get_int( - GetGlobalConfig(), "Accessibility", "MixerGreen")); + App()->GetUserConfig(), "Accessibility", "MixerGreen")); } else { backgroundNominalColor = p_backgroundNominalColor; } @@ -487,10 +488,11 @@ void VolumeMeter::setBackgroundWarningColor(QColor c) { p_backgroundWarningColor = std::move(c); - if (config_get_bool(GetGlobalConfig(), "Accessibility", + if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - backgroundWarningColor = color_from_int(config_get_int( - GetGlobalConfig(), "Accessibility", "MixerYellow")); + backgroundWarningColor = color_from_int( + config_get_int(App()->GetUserConfig(), "Accessibility", + "MixerYellow")); } else { backgroundWarningColor = p_backgroundWarningColor; } @@ -515,10 +517,10 @@ void VolumeMeter::setBackgroundErrorColor(QColor c) { p_backgroundErrorColor = std::move(c); - if (config_get_bool(GetGlobalConfig(), "Accessibility", + if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { backgroundErrorColor = color_from_int(config_get_int( - GetGlobalConfig(), "Accessibility", "MixerRed")); + App()->GetUserConfig(), "Accessibility", "MixerRed")); } else { backgroundErrorColor = p_backgroundErrorColor; } @@ -543,10 +545,10 @@ void VolumeMeter::setForegroundNominalColor(QColor c) { p_foregroundNominalColor = std::move(c); - if (config_get_bool(GetGlobalConfig(), "Accessibility", + if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { foregroundNominalColor = color_from_int( - config_get_int(GetGlobalConfig(), "Accessibility", + config_get_int(App()->GetUserConfig(), "Accessibility", "MixerGreenActive")); } else { foregroundNominalColor = p_foregroundNominalColor; @@ -572,10 +574,10 @@ void VolumeMeter::setForegroundWarningColor(QColor c) { p_foregroundWarningColor = std::move(c); - if (config_get_bool(GetGlobalConfig(), "Accessibility", + if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { foregroundWarningColor = color_from_int( - config_get_int(GetGlobalConfig(), "Accessibility", + config_get_int(App()->GetUserConfig(), "Accessibility", "MixerYellowActive")); } else { foregroundWarningColor = p_foregroundWarningColor; @@ -601,10 +603,11 @@ void VolumeMeter::setForegroundErrorColor(QColor c) { p_foregroundErrorColor = std::move(c); - if (config_get_bool(GetGlobalConfig(), "Accessibility", + if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - foregroundErrorColor = color_from_int(config_get_int( - GetGlobalConfig(), "Accessibility", "MixerRedActive")); + foregroundErrorColor = color_from_int( + config_get_int(App()->GetUserConfig(), "Accessibility", + "MixerRedActive")); } else { foregroundErrorColor = p_foregroundErrorColor; } diff --git a/UI/window-basic-adv-audio.cpp b/UI/window-basic-adv-audio.cpp index cc3cb3aaf848d4..b93fe3b14e66cb 100644 --- a/UI/window-basic-adv-audio.cpp +++ b/UI/window-basic-adv-audio.cpp @@ -24,7 +24,7 @@ OBSBasicAdvAudio::OBSBasicAdvAudio(QWidget *parent) sigs.emplace_back(sh, "source_deactivate", OBSSourceRemoved, this); VolumeType volType = (VolumeType)config_get_int( - GetGlobalConfig(), "BasicWindow", "AdvAudioVolumeType"); + App()->GetUserConfig(), "BasicWindow", "AdvAudioVolumeType"); if (volType == VolumeType::Percent) ui->usePercent->setChecked(true); @@ -140,8 +140,8 @@ void OBSBasicAdvAudio::on_usePercent_toggled(bool checked) for (size_t i = 0; i < controls.size(); i++) controls[i]->SetVolumeWidget(type); - config_set_int(GetGlobalConfig(), "BasicWindow", "AdvAudioVolumeType", - (int)type); + config_set_int(App()->GetUserConfig(), "BasicWindow", + "AdvAudioVolumeType", (int)type); } void OBSBasicAdvAudio::on_activeOnly_toggled(bool checked) diff --git a/UI/window-basic-auto-config.cpp b/UI/window-basic-auto-config.cpp index 82907841d25de0..1133cddd6b3902 100644 --- a/UI/window-basic-auto-config.cpp +++ b/UI/window-basic-auto-config.cpp @@ -39,18 +39,24 @@ extern QCefCookieManager *panel_cookies; /* ------------------------------------------------------------------------- */ -#define SERVICE_PATH "service.json" +constexpr std::string_view OBSServiceFileName = "service.json"; static OBSData OpenServiceSettings(std::string &type) { - char serviceJsonPath[512]; - int ret = GetProfilePath(serviceJsonPath, sizeof(serviceJsonPath), - SERVICE_PATH); - if (ret <= 0) + const OBSBasic *basic = + reinterpret_cast(App()->GetMainWindow()); + const OBSProfile ¤tProfile = basic->GetCurrentProfile(); + + const std::filesystem::path jsonFilePath = + currentProfile.path / + std::filesystem::u8path(OBSServiceFileName); + + if (!std::filesystem::exists(jsonFilePath)) { return OBSData(); + } - OBSDataAutoRelease data = - obs_data_create_from_json_file_safe(serviceJsonPath, "bak"); + OBSDataAutoRelease data = obs_data_create_from_json_file_safe( + jsonFilePath.u8string().c_str(), "bak"); obs_data_set_default_string(data, "type", "rtmp_common"); type = obs_data_get_string(data, "type"); diff --git a/UI/window-basic-interaction.cpp b/UI/window-basic-interaction.cpp index 716cbf24176fe0..b51b0f1f324778 100644 --- a/UI/window-basic-interaction.cpp +++ b/UI/window-basic-interaction.cpp @@ -44,10 +44,10 @@ OBSBasicInteraction::OBSBasicInteraction(QWidget *parent, OBSSource source_) OBSBasicInteraction::SourceRenamed, this), eventFilter(BuildEventFilter()) { - int cx = (int)config_get_int(App()->GlobalConfig(), "InteractionWindow", - "cx"); - int cy = (int)config_get_int(App()->GlobalConfig(), "InteractionWindow", - "cy"); + int cx = (int)config_get_int(App()->GetUserConfig(), + "InteractionWindow", "cx"); + int cy = (int)config_get_int(App()->GetUserConfig(), + "InteractionWindow", "cy"); Qt::WindowFlags flags = windowFlags(); Qt::WindowFlags helpFlag = Qt::WindowContextHelpButtonHint; @@ -166,9 +166,9 @@ void OBSBasicInteraction::closeEvent(QCloseEvent *event) if (!event->isAccepted()) return; - config_set_int(App()->GlobalConfig(), "InteractionWindow", "cx", + config_set_int(App()->GetAppConfig(), "InteractionWindow", "cx", width()); - config_set_int(App()->GlobalConfig(), "InteractionWindow", "cy", + config_set_int(App()->GetAppConfig(), "InteractionWindow", "cy", height()); obs_display_remove_draw_callback(ui->preview->GetDisplay(), diff --git a/UI/window-basic-main-outputs.cpp b/UI/window-basic-main-outputs.cpp index aad5fcfb85dfce..9177f108421d61 100644 --- a/UI/window-basic-main-outputs.cpp +++ b/UI/window-basic-main-outputs.cpp @@ -1246,8 +1246,9 @@ bool SimpleOutput::IsVodTrackEnabled(obs_service_t *service) config_get_bool(main->Config(), "SimpleOutput", "UseAdvanced"); bool enable = config_get_bool(main->Config(), "SimpleOutput", "VodTrackEnabled"); - bool enableForCustomServer = config_get_bool( - GetGlobalConfig(), "General", "EnableCustomServerVodTrack"); + bool enableForCustomServer = + config_get_bool(App()->GetUserConfig(), "General", + "EnableCustomServerVodTrack"); OBSDataAutoRelease settings = obs_service_get_settings(service); const char *name = obs_data_get_string(settings, "service"); @@ -1615,19 +1616,28 @@ struct AdvancedOutput : BasicOutputHandler { static OBSData GetDataFromJsonFile(const char *jsonFile) { - char fullPath[512]; + const OBSBasic *basic = + reinterpret_cast(App()->GetMainWindow()); + + const OBSProfile ¤tProfile = basic->GetCurrentProfile(); + + const std::filesystem::path jsonFilePath = + currentProfile.path / std::filesystem::u8path(jsonFile); + OBSDataAutoRelease data = nullptr; - int ret = GetProfilePath(fullPath, sizeof(fullPath), jsonFile); - if (ret > 0) { - BPtr jsonData = os_quick_read_utf8_file(fullPath); + if (!jsonFilePath.empty()) { + BPtr jsonData = os_quick_read_utf8_file( + jsonFilePath.u8string().c_str()); + if (!!jsonData) { data = obs_data_create_from_json(jsonData); } } - if (!data) + if (!data) { data = obs_data_create(); + } return data.Get(); } @@ -2252,8 +2262,9 @@ AdvancedOutput::VodTrackMixerIdx(obs_service_t *service) config_get_bool(main->Config(), "AdvOut", "VodTrackEnabled"); int vodTrackIndex = config_get_int(main->Config(), "AdvOut", "VodTrackIndex"); - bool enableForCustomServer = config_get_bool( - GetGlobalConfig(), "General", "EnableCustomServerVodTrack"); + bool enableForCustomServer = + config_get_bool(App()->GetUserConfig(), "General", + "EnableCustomServerVodTrack"); const char *id = obs_service_get_id(service); if (strcmp(id, "rtmp_custom") == 0) { diff --git a/UI/window-basic-main-profiles.cpp b/UI/window-basic-main-profiles.cpp index 7492e51d7e4c48..3a54e542269415 100644 --- a/UI/window-basic-main-profiles.cpp +++ b/UI/window-basic-main-profiles.cpp @@ -15,6 +15,11 @@ along with this program. If not, see . ******************************************************************************/ +#include +#include +#include +#include +#include #include #include #include @@ -26,792 +31,767 @@ #include "window-basic-auto-config.hpp" #include "window-namedialog.hpp" +// MARK: Constant Expressions + +constexpr std::string_view OBSProfilePath = "/obs-studio/basic/profiles/"; +constexpr std::string_view OBSProfileSettingsFile = "basic.ini"; + +// MARK: Forward Declarations + extern void DestroyPanelCookieManager(); extern void DuplicateCurrentCookieProfile(ConfigFile &config); extern void CheckExistingCookieId(); extern void DeleteCookies(); -void EnumProfiles(std::function &&cb) +// MARK: - Main Profile Management Functions + +void OBSBasic::SetupNewProfile(const std::string &profileName, bool useWizard) { - char path[512]; - os_glob_t *glob; + const OBSProfile &newProfile = CreateProfile(profileName); - int ret = GetConfigPath(path, sizeof(path), - "obs-studio/basic/profiles/*"); - if (ret <= 0) { - blog(LOG_WARNING, "Failed to get profiles config path"); - return; - } + config_set_bool(App()->GetUserConfig(), "Basic", "ConfigOnNewProfile", + useWizard); - if (os_glob(path, 0, &glob) != 0) { - blog(LOG_WARNING, "Failed to glob profiles"); - return; + ActivateProfile(newProfile, true); + + blog(LOG_INFO, "Created profile '%s' (clean, %s)", + newProfile.name.c_str(), newProfile.directoryName.c_str()); + blog(LOG_INFO, "------------------------------------------------"); + + if (useWizard) { + AutoConfig wizard(this); + wizard.setModal(true); + wizard.show(); + wizard.exec(); } +} - for (size_t i = 0; i < glob->gl_pathc; i++) { - const char *filePath = glob->gl_pathv[i].path; - const char *dirName = strrchr(filePath, '/') + 1; +void OBSBasic::SetupDuplicateProfile(const std::string &profileName) +{ + const OBSProfile &newProfile = CreateProfile(profileName); + const OBSProfile ¤tProfile = GetCurrentProfile(); - if (!glob->gl_pathv[i].directory) - continue; + const auto copyOptions = + std::filesystem::copy_options::recursive | + std::filesystem::copy_options::overwrite_existing; - if (strcmp(dirName, ".") == 0 || strcmp(dirName, "..") == 0) - continue; + try { + std::filesystem::copy(currentProfile.path, newProfile.path, + copyOptions); + } catch (const std::filesystem::filesystem_error &error) { + blog(LOG_DEBUG, "%s", error.what()); + throw std::logic_error( + "Failed to copy files for cloned profile: " + + newProfile.name); + } - std::string file = filePath; - file += "/basic.ini"; + ActivateProfile(newProfile); - ConfigFile config; - int ret = config.Open(file.c_str(), CONFIG_OPEN_EXISTING); - if (ret != CONFIG_SUCCESS) - continue; + blog(LOG_INFO, "Created profile '%s' (duplicate, %s)", + newProfile.name.c_str(), newProfile.directoryName.c_str()); + blog(LOG_INFO, "------------------------------------------------"); +} + +void OBSBasic::SetupRenameProfile(const std::string &profileName) +{ + const OBSProfile &newProfile = CreateProfile(profileName); + const OBSProfile currentProfile = GetCurrentProfile(); - const char *name = config_get_string(config, "General", "Name"); - if (!name) - name = strrchr(filePath, '/') + 1; + const auto copyOptions = + std::filesystem::copy_options::recursive | + std::filesystem::copy_options::overwrite_existing; - if (!cb(name, filePath)) - break; + try { + std::filesystem::copy(currentProfile.path, newProfile.path, + copyOptions); + } catch (const std::filesystem::filesystem_error &error) { + blog(LOG_DEBUG, "%s", error.what()); + throw std::logic_error("Failed to copy files for profile: " + + currentProfile.name); } - os_globfree(glob); -} + profiles.erase(currentProfile.name); -static bool GetProfileDir(const char *findName, const char *&profileDir) -{ - bool found = false; - auto func = [&](const char *name, const char *path) { - if (strcmp(name, findName) == 0) { - found = true; - profileDir = strrchr(path, '/') + 1; - return false; - } - return true; - }; + ActivateProfile(newProfile); + RemoveProfile(currentProfile); - EnumProfiles(func); - return found; -} + blog(LOG_INFO, "Renamed profile '%s' to '%s' (%s)", + currentProfile.name.c_str(), newProfile.name.c_str(), + newProfile.directoryName.c_str()); + blog(LOG_INFO, "------------------------------------------------"); -static bool ProfileExists(const char *findName) -{ - const char *profileDir = nullptr; - return GetProfileDir(findName, profileDir); + OnEvent(OBS_FRONTEND_EVENT_PROFILE_RENAMED); } -static bool AskForProfileName(QWidget *parent, std::string &name, - const char *title, const char *text, - const bool showWizard, bool &wizardChecked, - const char *oldName = nullptr) +// MARK: - Profile File Management Functions + +const OBSProfile &OBSBasic::CreateProfile(const std::string &profileName) { - for (;;) { - bool success = false; - - if (showWizard) { - success = NameDialog::AskForNameWithOption( - parent, title, text, name, - QTStr("AddProfile.WizardCheckbox"), - wizardChecked, QT_UTF8(oldName)); - } else { - success = NameDialog::AskForName( - parent, title, text, name, QT_UTF8(oldName)); - } + if (const auto &foundProfile = GetProfileByName(profileName)) { + throw std::invalid_argument("Profile already exists: " + + profileName); + } - if (!success) { - return false; - } - if (name.empty()) { - OBSMessageBox::warning(parent, - QTStr("NoNameEntered.Title"), - QTStr("NoNameEntered.Text")); - continue; - } - if (ProfileExists(name.c_str())) { - OBSMessageBox::warning(parent, - QTStr("NameExists.Title"), - QTStr("NameExists.Text")); - continue; - } - break; + std::string directoryName; + if (!GetFileSafeName(profileName.c_str(), directoryName)) { + throw std::invalid_argument( + "Failed to create safe directory for new profile: " + + profileName); } - return true; -} -static bool FindSafeProfileDirName(const std::string &profileName, - std::string &dirName) -{ - char path[512]; - int ret; + std::string profileDirectory; + profileDirectory.reserve(App()->userProfilesLocation.u8string().size() + + OBSProfilePath.size() + directoryName.size()); + profileDirectory.append(App()->userProfilesLocation.u8string()) + .append(OBSProfilePath) + .append(directoryName); - if (ProfileExists(profileName.c_str())) { - blog(LOG_WARNING, "Profile '%s' exists", profileName.c_str()); - return false; + if (!GetClosestUnusedFileName(profileDirectory, nullptr)) { + throw std::invalid_argument( + "Failed to get closest directory name for new profile: " + + profileName); } - if (!GetFileSafeName(profileName.c_str(), dirName)) { - blog(LOG_WARNING, "Failed to create safe file name for '%s'", - profileName.c_str()); - return false; - } + const std::filesystem::path profileDirectoryPath = + std::filesystem::u8path(profileDirectory); - ret = GetConfigPath(path, sizeof(path), "obs-studio/basic/profiles/"); - if (ret <= 0) { - blog(LOG_WARNING, "Failed to get profiles config path"); - return false; + try { + std::filesystem::create_directory(profileDirectoryPath); + } catch (const std::filesystem::filesystem_error error) { + throw std::logic_error( + "Failed to create directory for new profile: " + + profileDirectory); } - dirName.insert(0, path); + const std::filesystem::path profileFile = + profileDirectoryPath / + std::filesystem::u8path(OBSProfileSettingsFile); - if (!GetClosestUnusedFileName(dirName, nullptr)) { - blog(LOG_WARNING, "Failed to get closest file name for %s", - dirName.c_str()); - return false; - } + auto [iterator, success] = profiles.try_emplace( + profileName, + OBSProfile{profileName, + profileDirectoryPath.filename().u8string(), + profileDirectoryPath, profileFile}); + + OnEvent(OBS_FRONTEND_EVENT_PROFILE_LIST_CHANGED); - dirName.erase(0, ret); - return true; + return iterator->second; } -static bool CopyProfile(const char *fromPartial, const char *to) +void OBSBasic::RemoveProfile(OBSProfile profile) { - os_glob_t *glob; - char path[514]; - char dir[512]; - int ret; - - ret = GetConfigPath(dir, sizeof(dir), "obs-studio/basic/profiles/"); - if (ret <= 0) { - blog(LOG_WARNING, "Failed to get profiles config path"); - return false; + try { + std::filesystem::remove_all(profile.path); + } catch (const std::filesystem::filesystem_error &error) { + blog(LOG_DEBUG, "%s", error.what()); + throw std::logic_error("Failed to remove profile directory: " + + profile.directoryName); } - snprintf(path, sizeof(path), "%s%s/*", dir, fromPartial); + blog(LOG_INFO, "------------------------------------------------"); + blog(LOG_INFO, "Removed profile '%s' (%s)", profile.name.c_str(), + profile.directoryName.c_str()); + blog(LOG_INFO, "------------------------------------------------"); +} + +// MARK: - Profile UI Handling Functions - if (os_glob(path, 0, &glob) != 0) { - blog(LOG_WARNING, "Failed to glob profile '%s'", fromPartial); +bool OBSBasic::CreateNewProfile(const QString &name) +{ + try { + SetupNewProfile(name.toStdString()); + return true; + } catch (const std::invalid_argument &error) { + blog(LOG_ERROR, "%s", error.what()); + return false; + } catch (const std::logic_error &error) { + blog(LOG_ERROR, "%s", error.what()); return false; } +} - for (size_t i = 0; i < glob->gl_pathc; i++) { - const char *filePath = glob->gl_pathv[i].path; - if (glob->gl_pathv[i].directory) - continue; - - ret = snprintf(path, sizeof(path), "%s/%s", to, - strrchr(filePath, '/') + 1); - if (ret > 0) { - if (os_copyfile(filePath, path) != 0) { - blog(LOG_WARNING, - "CopyProfile: Failed to " - "copy file %s to %s", - filePath, path); - } - } +bool OBSBasic::CreateDuplicateProfile(const QString &name) +{ + try { + SetupDuplicateProfile(name.toStdString()); + return true; + } catch (const std::invalid_argument &error) { + blog(LOG_ERROR, "%s", error.what()); + return false; + } catch (const std::logic_error &error) { + blog(LOG_ERROR, "%s", error.what()); + return false; } - - os_globfree(glob); - - return true; } -static bool ProfileNeedsRestart(config_t *newConfig, QString &settings) +void OBSBasic::DeleteProfile(const QString &name) { - OBSBasic *main = OBSBasic::Get(); - - const char *oldSpeakers = - config_get_string(main->Config(), "Audio", "ChannelSetup"); - uint oldSampleRate = - config_get_uint(main->Config(), "Audio", "SampleRate"); + const std::string_view currentProfileName{ + config_get_string(App()->GetUserConfig(), "Basic", "Profile")}; - const char *newSpeakers = - config_get_string(newConfig, "Audio", "ChannelSetup"); - uint newSampleRate = config_get_uint(newConfig, "Audio", "SampleRate"); + if (currentProfileName == name.toStdString()) { + on_actionRemoveProfile_triggered(); + return; + } - auto appendSetting = [&settings](const char *name) { - settings += QStringLiteral("\n") + QTStr(name); - }; + auto foundProfile = GetProfileByName(name.toStdString()); - bool result = false; - if (oldSpeakers != NULL && newSpeakers != NULL) { - result = strcmp(oldSpeakers, newSpeakers) != 0; - appendSetting("Basic.Settings.Audio.Channels"); - } - if (oldSampleRate != 0 && newSampleRate != 0) { - result |= oldSampleRate != newSampleRate; - appendSetting("Basic.Settings.Audio.SampleRate"); + if (!foundProfile) { + blog(LOG_ERROR, "Invalid profile name: %s", QT_TO_UTF8(name)); + return; } - return result; -} + RemoveProfile(foundProfile.value()); + profiles.erase(name.toStdString()); -bool OBSBasic::AddProfile(bool create_new, const char *title, const char *text, - const char *init_text, bool rename) -{ - std::string name; - - bool showWizardChecked = config_get_bool(App()->GlobalConfig(), "Basic", - "ConfigOnNewProfile"); + RefreshProfiles(); - if (!AskForProfileName(this, name, title, text, create_new, - showWizardChecked, init_text)) - return false; + config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - return CreateProfile(name, create_new, showWizardChecked, rename); + OnEvent(OBS_FRONTEND_EVENT_PROFILE_LIST_CHANGED); } -bool OBSBasic::CreateProfile(const std::string &newName, bool create_new, - bool showWizardChecked, bool rename) +void OBSBasic::ChangeProfile() { - std::string newDir; - std::string newPath; - ConfigFile config; - - if (!FindSafeProfileDirName(newName, newDir)) - return false; + QAction *action = reinterpret_cast(sender()); - if (create_new) { - config_set_bool(App()->GlobalConfig(), "Basic", - "ConfigOnNewProfile", showWizardChecked); + if (!action) { + return; } - std::string curDir = - config_get_string(App()->GlobalConfig(), "Basic", "ProfileDir"); + const std::string_view currentProfileName{ + config_get_string(App()->GetUserConfig(), "Basic", "Profile")}; + const std::string selectedProfileName{action->text().toStdString()}; - char baseDir[512]; - int ret = GetConfigPath(baseDir, sizeof(baseDir), - "obs-studio/basic/profiles/"); - if (ret <= 0) { - blog(LOG_WARNING, "Failed to get profiles config path"); - return false; + if (currentProfileName == selectedProfileName) { + action->setChecked(true); + return; } - newPath = baseDir; - newPath += newDir; + const std::optional foundProfile = + GetProfileByName(selectedProfileName); - if (os_mkdir(newPath.c_str()) < 0) { - blog(LOG_WARNING, "Failed to create profile directory '%s'", - newDir.c_str()); - return false; + if (!foundProfile) { + const std::string errorMessage{"Selected profile not found: "}; + + throw std::invalid_argument(errorMessage + + currentProfileName.data()); } - if (!create_new) - CopyProfile(curDir.c_str(), newPath.c_str()); + const OBSProfile &selectedProfile = foundProfile.value(); - newPath += "/basic.ini"; + OnEvent(OBS_FRONTEND_EVENT_PROFILE_CHANGING); - if (config.Open(newPath.c_str(), CONFIG_OPEN_ALWAYS) != 0) { - blog(LOG_ERROR, "Failed to open new config file '%s'", - newDir.c_str()); - return false; - } + ActivateProfile(selectedProfile, true); - if (!rename) - OnEvent(OBS_FRONTEND_EVENT_PROFILE_CHANGING); + blog(LOG_INFO, "Switched to profile '%s' (%s)", + selectedProfile.name.c_str(), + selectedProfile.directoryName.c_str()); + blog(LOG_INFO, "------------------------------------------------"); +} - config_set_string(App()->GlobalConfig(), "Basic", "Profile", - newName.c_str()); - config_set_string(App()->GlobalConfig(), "Basic", "ProfileDir", - newDir.c_str()); +void OBSBasic::RefreshProfiles(bool refreshCache) +{ + std::string_view currentProfileName{ + config_get_string(App()->GetUserConfig(), "Basic", "Profile")}; - Auth::Save(); - if (create_new) { - auth.reset(); - DestroyPanelCookieManager(); -#ifdef YOUTUBE_ENABLED - if (youtubeAppDock) - DeleteYouTubeAppDock(); -#endif - } else if (!rename) { - DuplicateCurrentCookieProfile(config); - } + QList menuActions = ui->profileMenu->actions(); - config_set_string(config, "General", "Name", newName.c_str()); - basicConfig.SaveSafe("tmp"); - config.SaveSafe("tmp"); - config.Swap(basicConfig); - InitBasicConfigDefaults(); - InitBasicConfigDefaults2(); - RefreshProfiles(); + for (auto &action : menuActions) { + QVariant variant = action->property("file_name"); - if (create_new) - ResetProfileData(); + if (variant.typeName() != nullptr) { + delete action; + } + } - blog(LOG_INFO, "Created profile '%s' (%s, %s)", newName.c_str(), - create_new ? "clean" : "duplicate", newDir.c_str()); - blog(LOG_INFO, "------------------------------------------------"); + if (refreshCache) { + RefreshProfileCache(); + } - config_save_safe(App()->GlobalConfig(), "tmp", nullptr); - UpdateTitleBar(); - UpdateVolumeControlsDecayRate(); + size_t numAddedProfiles = 0; + for (auto &[profileName, profile] : profiles) { + QAction *action = + new QAction(QString().fromStdString(profileName), this); + action->setProperty( + "file_name", + QString().fromStdString(profile.directoryName)); + connect(action, &QAction::triggered, this, + &OBSBasic::ChangeProfile); + action->setCheckable(true); + action->setChecked(profileName == currentProfileName); - Auth::Load(); + ui->profileMenu->addAction(action); - // Run auto configuration setup wizard when a new profile is made to assist - // setting up blank settings - if (create_new && showWizardChecked) { - AutoConfig wizard(this); - wizard.setModal(true); - wizard.show(); - wizard.exec(); + numAddedProfiles += 1; } - if (!rename) { - OnEvent(OBS_FRONTEND_EVENT_PROFILE_LIST_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PROFILE_CHANGED); - } - return true; + ui->actionRemoveProfile->setEnabled(numAddedProfiles > 1); } -bool OBSBasic::NewProfile(const QString &name) -{ - return CreateProfile(name.toStdString(), true, false, false); -} +// MARK: - Profile Cache Functions -bool OBSBasic::DuplicateProfile(const QString &name) +/// Refreshes profile cache data with profile state found on local file system. +void OBSBasic::RefreshProfileCache() { - return CreateProfile(name.toStdString(), false, false, false); -} + std::map foundProfiles{}; -void OBSBasic::DeleteProfile(const char *profileName, const char *profileDir) -{ - char profilePath[512]; - char basePath[512]; + const std::filesystem::path profilesPath = + App()->userProfilesLocation / + std::filesystem::u8path(OBSProfilePath.substr(1)); - int ret = GetConfigPath(basePath, sizeof(basePath), - "obs-studio/basic/profiles"); - if (ret <= 0) { + if (!std::filesystem::exists(profilesPath)) { blog(LOG_WARNING, "Failed to get profiles config path"); return; } - ret = snprintf(profilePath, sizeof(profilePath), "%s/%s/*", basePath, - profileDir); - if (ret <= 0) { - blog(LOG_WARNING, "Failed to get path for profile dir '%s'", - profileDir); - return; - } + for (const auto &entry : + std::filesystem::directory_iterator(profilesPath)) { + if (!entry.is_directory()) { + continue; + } - os_glob_t *glob; - if (os_glob(profilePath, 0, &glob) != 0) { - blog(LOG_WARNING, "Failed to glob profile dir '%s'", - profileDir); - return; - } + std::string profileCandidate; + profileCandidate.reserve(entry.path().u8string().size() + + OBSProfileSettingsFile.size() + 1); + profileCandidate.append(entry.path().u8string()) + .append("/") + .append(OBSProfileSettingsFile); - for (size_t i = 0; i < glob->gl_pathc; i++) { - const char *filePath = glob->gl_pathv[i].path; + ConfigFile config; - if (glob->gl_pathv[i].directory) + if (config.Open(profileCandidate.c_str(), + CONFIG_OPEN_EXISTING) != CONFIG_SUCCESS) { continue; + } - os_unlink(filePath); - } + std::string candidateName; + const char *configName = + config_get_string(config, "General", "Name"); - os_globfree(glob); + if (configName) { + candidateName = configName; + } else { + candidateName = entry.path().filename().u8string(); + } - ret = snprintf(profilePath, sizeof(profilePath), "%s/%s", basePath, - profileDir); - if (ret <= 0) { - blog(LOG_WARNING, "Failed to get path for profile dir '%s'", - profileDir); - return; + foundProfiles.try_emplace( + candidateName, + OBSProfile{candidateName, + entry.path().filename().u8string(), + entry.path(), profileCandidate}); } - os_rmdir(profilePath); - - blog(LOG_INFO, "------------------------------------------------"); - blog(LOG_INFO, "Removed profile '%s' (%s)", profileName, profileDir); - blog(LOG_INFO, "------------------------------------------------"); + profiles.swap(foundProfiles); } -void OBSBasic::DeleteProfile(const QString &profileName) +const OBSProfile &OBSBasic::GetCurrentProfile() const { - std::string name = profileName.toStdString(); - const char *curName = - config_get_string(App()->GlobalConfig(), "Basic", "Profile"); + std::string currentProfileName{ + config_get_string(App()->GetUserConfig(), "Basic", "Profile")}; - if (strcmp(curName, name.c_str()) == 0) { - on_actionRemoveProfile_triggered(true); - return; + if (currentProfileName.empty()) { + throw std::invalid_argument( + "No valid profile name in configuration Basic->Profile"); } - const char *profileDir = nullptr; - if (!GetProfileDir(name.c_str(), profileDir)) { - blog(LOG_WARNING, "Profile '%s' not found", name.c_str()); - return; - } + const auto &foundProfile = profiles.find(currentProfileName); - if (!profileDir) { - blog(LOG_WARNING, "Failed to get profile dir for profile '%s'", - name.c_str()); - return; + if (foundProfile != profiles.end()) { + return foundProfile->second; + } else { + throw std::invalid_argument( + "Profile not found in profile list: " + + currentProfileName); } - - DeleteProfile(name.c_str(), profileDir); - RefreshProfiles(); - config_save_safe(App()->GlobalConfig(), "tmp", nullptr); - OnEvent(OBS_FRONTEND_EVENT_PROFILE_LIST_CHANGED); } -void OBSBasic::RefreshProfiles() +std::optional +OBSBasic::GetProfileByName(const std::string &profileName) const { - QList menuActions = ui->profileMenu->actions(); - int count = 0; + auto foundProfile = profiles.find(profileName); - for (int i = 0; i < menuActions.count(); i++) { - QVariant v = menuActions[i]->property("file_name"); - if (v.typeName() != nullptr) - delete menuActions[i]; + if (foundProfile == profiles.end()) { + return {}; + } else { + return foundProfile->second; } +} - const char *curName = - config_get_string(App()->GlobalConfig(), "Basic", "Profile"); - - auto addProfile = [&](const char *name, const char *path) { - std::string file = strrchr(path, '/') + 1; - - QAction *action = new QAction(QT_UTF8(name), this); - action->setProperty("file_name", QT_UTF8(path)); - connect(action, &QAction::triggered, this, - &OBSBasic::ChangeProfile); - action->setCheckable(true); +std::optional +OBSBasic::GetProfileByDirectoryName(const std::string &directoryName) const +{ + for (auto &[iterator, profile] : profiles) { + if (profile.directoryName == directoryName) { + return profile; + } + } - action->setChecked(strcmp(name, curName) == 0); + return {}; +} - ui->profileMenu->addAction(action); - count++; - return true; - }; +// MARK: - Qt Slot Functions - EnumProfiles(addProfile); +void OBSBasic::on_actionNewProfile_triggered() +{ + bool useProfileWizard = config_get_bool(App()->GetUserConfig(), "Basic", + "ConfigOnNewProfile"); - ui->actionRemoveProfile->setEnabled(count > 1); -} + const OBSPromptCallback profilePromptCallback = + [this](const OBSPromptResult &result) { + if (GetProfileByName(result.promptValue)) { + return false; + } -void OBSBasic::ResetProfileData() -{ - ResetVideo(); - service = nullptr; - InitService(); - ResetOutputs(); - ClearHotkeys(); - CreateHotkeys(); + return true; + }; - /* load audio monitoring */ - if (obs_audio_monitoring_available()) { - const char *device_name = config_get_string( - basicConfig, "Audio", "MonitoringDeviceName"); - const char *device_id = config_get_string(basicConfig, "Audio", - "MonitoringDeviceId"); + const OBSPromptRequest request{Str("AddProfile.Title"), + Str("AddProfile.Text"), + "", + true, + Str("AddProfile.WizardCheckbox"), + useProfileWizard}; - obs_set_audio_monitoring_device(device_name, device_id); + OBSPromptResult result = PromptForName(request, profilePromptCallback); - blog(LOG_INFO, "Audio monitoring device:\n\tname: %s\n\tid: %s", - device_name, device_id); + if (!result.success) { + return; } -} -void OBSBasic::on_actionNewProfile_triggered() -{ - AddProfile(true, Str("AddProfile.Title"), Str("AddProfile.Text")); + try { + SetupNewProfile(result.promptValue, result.optionValue); + } catch (const std::invalid_argument &error) { + blog(LOG_ERROR, "%s", error.what()); + } catch (const std::logic_error &error) { + blog(LOG_ERROR, "%s", error.what()); + } } void OBSBasic::on_actionDupProfile_triggered() { - AddProfile(false, Str("AddProfile.Title"), Str("AddProfile.Text")); -} + const OBSPromptCallback profilePromptCallback = + [this](const OBSPromptResult &result) { + if (GetProfileByName(result.promptValue)) { + return false; + } -void OBSBasic::on_actionRenameProfile_triggered() -{ - std::string curDir = - config_get_string(App()->GlobalConfig(), "Basic", "ProfileDir"); - std::string curName = - config_get_string(App()->GlobalConfig(), "Basic", "Profile"); + return true; + }; + + const OBSPromptRequest request{Str("AddProfile.Title"), + Str("AddProfile.Text")}; - /* Duplicate and delete in case there are any issues in the process */ - bool success = AddProfile(false, Str("RenameProfile.Title"), - Str("AddProfile.Text"), curName.c_str(), - true); - if (success) { - DeleteProfile(curName.c_str(), curDir.c_str()); - RefreshProfiles(); + OBSPromptResult result = PromptForName(request, profilePromptCallback); + + if (!result.success) { + return; } - OnEvent(OBS_FRONTEND_EVENT_PROFILE_RENAMED); + try { + SetupDuplicateProfile(result.promptValue); + } catch (const std::invalid_argument &error) { + blog(LOG_ERROR, "%s", error.what()); + } catch (const std::logic_error &error) { + blog(LOG_ERROR, "%s", error.what()); + } } -void OBSBasic::on_actionRemoveProfile_triggered(bool skipConfirmation) +void OBSBasic::on_actionRenameProfile_triggered() { - std::string newName; - std::string newPath; - ConfigFile config; + const std::string currentProfileName = + config_get_string(App()->GetUserConfig(), "Basic", "Profile"); - std::string oldDir = - config_get_string(App()->GlobalConfig(), "Basic", "ProfileDir"); - std::string oldName = - config_get_string(App()->GlobalConfig(), "Basic", "Profile"); + const OBSPromptCallback profilePromptCallback = + [this](const OBSPromptResult &result) { + if (GetProfileByName(result.promptValue)) { + return false; + } - auto cb = [&](const char *name, const char *filePath) { - if (strcmp(oldName.c_str(), name) != 0) { - newName = name; - newPath = filePath; - return false; - } + return true; + }; - return true; - }; + const OBSPromptRequest request{Str("RenameProfile.Title"), + Str("RenameProfile.Text"), + currentProfileName}; - EnumProfiles(cb); + OBSPromptResult result = PromptForName(request, profilePromptCallback); - /* this should never be true due to menu item being grayed out */ - if (newPath.empty()) + if (!result.success) { return; - - if (!skipConfirmation) { - QString text = QTStr("ConfirmRemove.Text") - .arg(QT_UTF8(oldName.c_str())); - - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmRemove.Title"), text); - if (button == QMessageBox::No) - return; } - size_t newPath_len = newPath.size(); - newPath += "/basic.ini"; + try { + SetupRenameProfile(result.promptValue); + } catch (const std::invalid_argument &error) { + blog(LOG_ERROR, "%s", error.what()); + } catch (const std::logic_error &error) { + blog(LOG_ERROR, "%s", error.what()); + } +} - if (config.Open(newPath.c_str(), CONFIG_OPEN_ALWAYS) != 0) { - blog(LOG_ERROR, "ChangeProfile: Failed to load file '%s'", - newPath.c_str()); +void OBSBasic::on_actionRemoveProfile_triggered(bool skipConfirmation) +{ + if (profiles.size() < 2) { return; } - OnEvent(OBS_FRONTEND_EVENT_PROFILE_CHANGING); - - newPath.resize(newPath_len); + OBSProfile currentProfile; - const char *newDir = strrchr(newPath.c_str(), '/') + 1; + try { + currentProfile = GetCurrentProfile(); - config_set_string(App()->GlobalConfig(), "Basic", "Profile", - newName.c_str()); - config_set_string(App()->GlobalConfig(), "Basic", "ProfileDir", newDir); + if (!skipConfirmation) { + const QString confirmationText = + QTStr("ConfirmRemove.Text") + .arg(QString::fromStdString( + currentProfile.name)); - QString settingsRequiringRestart; - bool needsRestart = - ProfileNeedsRestart(config, settingsRequiringRestart); + const QMessageBox::StandardButton button = + OBSMessageBox::question( + this, QTStr("ConfirmRemove.Title"), + confirmationText); - Auth::Save(); - auth.reset(); - DeleteCookies(); - DestroyPanelCookieManager(); - - config.Swap(basicConfig); - InitBasicConfigDefaults(); - InitBasicConfigDefaults2(); - ResetProfileData(); - DeleteProfile(oldName.c_str(), oldDir.c_str()); - RefreshProfiles(); - config_save_safe(App()->GlobalConfig(), "tmp", nullptr); + if (button == QMessageBox::No) { + return; + } + } - blog(LOG_INFO, "Switched to profile '%s' (%s)", newName.c_str(), - newDir); - blog(LOG_INFO, "------------------------------------------------"); + OnEvent(OBS_FRONTEND_EVENT_PROFILE_CHANGING); - UpdateTitleBar(); - UpdateVolumeControlsDecayRate(); + profiles.erase(currentProfile.name); + } catch (const std::invalid_argument &error) { + blog(LOG_ERROR, "%s", error.what()); + } catch (const std::logic_error &error) { + blog(LOG_ERROR, "%s", error.what()); + } - Auth::Load(); + const OBSProfile &newProfile = profiles.rbegin()->second; - OnEvent(OBS_FRONTEND_EVENT_PROFILE_LIST_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PROFILE_CHANGED); + ActivateProfile(newProfile, true); + RemoveProfile(currentProfile); - if (needsRestart) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("Restart"), - QTStr("LoadProfileNeedsRestart") - .arg(settingsRequiringRestart)); +#ifdef YOUTUBE_ENABLED + if (YouTubeAppDock::IsYTServiceSelected() && !youtubeAppDock) + NewYouTubeAppDock(); +#endif - if (button == QMessageBox::Yes) { - restart = true; - close(); - } - } + blog(LOG_INFO, "Switched to profile '%s' (%s)", newProfile.name.c_str(), + newProfile.directoryName.c_str()); + blog(LOG_INFO, "------------------------------------------------"); } void OBSBasic::on_actionImportProfile_triggered() { - char path[512]; - - QString home = QDir::homePath(); + const QString home = QDir::homePath(); - int ret = GetConfigPath(path, 512, "obs-studio/basic/profiles/"); - if (ret <= 0) { - blog(LOG_WARNING, "Failed to get profile config path"); - return; - } - - QString dir = SelectDirectory( + const QString sourceDirectory = SelectDirectory( this, QTStr("Basic.MainMenu.Profile.Import"), home); - if (!dir.isEmpty() && !dir.isNull()) { - QString inputPath = QString::fromUtf8(path); - QFileInfo finfo(dir); - QString directory = finfo.fileName(); - QString profileDir = inputPath + directory; + if (!sourceDirectory.isEmpty() && !sourceDirectory.isNull()) { + const std::filesystem::path sourcePath = + std::filesystem::u8path(sourceDirectory.toStdString()); + const std::string directoryName = + sourcePath.filename().string(); - if (ProfileExists(directory.toStdString().c_str())) { + if (auto profile = GetProfileByDirectoryName(directoryName)) { OBSMessageBox::warning( this, QTStr("Basic.MainMenu.Profile.Import"), QTStr("Basic.MainMenu.Profile.Exists")); - } else if (os_mkdir(profileDir.toStdString().c_str()) < 0) { + return; + } + + std::string destinationPathString; + destinationPathString.reserve( + App()->userProfilesLocation.u8string().size() + + OBSProfilePath.size() + directoryName.size()); + destinationPathString + .append(App()->userProfilesLocation.u8string()) + .append(OBSProfilePath) + .append(directoryName); + + const std::filesystem::path destinationPath = + std::filesystem::u8path(destinationPathString); + + try { + std::filesystem::create_directory(destinationPath); + } catch (const std::filesystem::filesystem_error &error) { blog(LOG_WARNING, - "Failed to create profile directory '%s'", - directory.toStdString().c_str()); - } else { - QFile::copy(dir + "/basic.ini", - profileDir + "/basic.ini"); - QFile::copy(dir + "/service.json", - profileDir + "/service.json"); - QFile::copy(dir + "/streamEncoder.json", - profileDir + "/streamEncoder.json"); - QFile::copy(dir + "/recordEncoder.json", - profileDir + "/recordEncoder.json"); - RefreshProfiles(); + "Failed to create profile directory '%s':\n%s", + directoryName.c_str(), error.what()); + return; } + + const std::array, 4> profileFiles{{ + {"basic.ini", true}, + {"service.json", false}, + {"streamEncoder.json", false}, + {"recordEncoder.json", false}, + }}; + + for (auto &[file, isMandatory] : profileFiles) { + const std::filesystem::path sourceFile = + sourcePath / std::filesystem::u8path(file); + + if (!std::filesystem::exists(sourceFile)) { + if (isMandatory) { + blog(LOG_ERROR, + "Failed to import profile from directory '%s' - necessary file '%s' not found", + directoryName.c_str(), + file.c_str()); + return; + } + continue; + } + + const std::filesystem::path destinationFile = + destinationPath / std::filesystem::u8path(file); + + try { + std::filesystem::copy(sourceFile, + destinationFile); + } catch ( + const std::filesystem::filesystem_error &error) { + blog(LOG_WARNING, + "Failed to copy import file '%s' for profile '%s':\n%s", + file.c_str(), directoryName.c_str(), + error.what()); + return; + } + } + + RefreshProfiles(true); } } void OBSBasic::on_actionExportProfile_triggered() { - char path[512]; + const OBSProfile ¤tProfile = GetCurrentProfile(); - QString home = QDir::homePath(); + const QString home = QDir::homePath(); - QString currentProfile = QString::fromUtf8(config_get_string( - App()->GlobalConfig(), "Basic", "ProfileDir")); + const QString destinationDirectory = SelectDirectory( + this, QTStr("Basic.MainMenu.Profile.Export"), home); - int ret = GetConfigPath(path, 512, "obs-studio/basic/profiles/"); - if (ret <= 0) { - blog(LOG_WARNING, "Failed to get profile config path"); - return; - } + const std::array profileFiles{"basic.ini", + "service.json", + "streamEncoder.json", + "recordEncoder.json"}; - QString dir = SelectDirectory( - this, QTStr("Basic.MainMenu.Profile.Export"), home); + if (!destinationDirectory.isEmpty() && !destinationDirectory.isNull()) { + const std::filesystem::path sourcePath = currentProfile.path; + const std::filesystem::path destinationPath = + std::filesystem::u8path( + destinationDirectory.toStdString()) / + std::filesystem::u8path(currentProfile.directoryName); - if (!dir.isEmpty() && !dir.isNull()) { - QString outputDir = dir + "/" + currentProfile; - QString inputPath = QString::fromUtf8(path); - QDir folder(outputDir); + if (!std::filesystem::exists(destinationPath)) { + std::filesystem::create_directory(destinationPath); + } - if (!folder.exists()) { - folder.mkpath(outputDir); - } else { - if (QFile::exists(outputDir + "/basic.ini")) - QFile::remove(outputDir + "/basic.ini"); + std::filesystem::copy_options copyOptions = + std::filesystem::copy_options::overwrite_existing; - if (QFile::exists(outputDir + "/service.json")) - QFile::remove(outputDir + "/service.json"); + for (auto &file : profileFiles) { + const std::filesystem::path sourceFile = + sourcePath / std::filesystem::u8path(file); - if (QFile::exists(outputDir + "/streamEncoder.json")) - QFile::remove(outputDir + - "/streamEncoder.json"); + if (!std::filesystem::exists(sourceFile)) { + continue; + } - if (QFile::exists(outputDir + "/recordEncoder.json")) - QFile::remove(outputDir + - "/recordEncoder.json"); - } + const std::filesystem::path destinationFile = + destinationPath / std::filesystem::u8path(file); - QFile::copy(inputPath + currentProfile + "/basic.ini", - outputDir + "/basic.ini"); - QFile::copy(inputPath + currentProfile + "/service.json", - outputDir + "/service.json"); - QFile::copy(inputPath + currentProfile + "/streamEncoder.json", - outputDir + "/streamEncoder.json"); - QFile::copy(inputPath + currentProfile + "/recordEncoder.json", - outputDir + "/recordEncoder.json"); + try { + std::filesystem::copy(sourceFile, + destinationFile, + copyOptions); + } catch ( + const std::filesystem::filesystem_error &error) { + blog(LOG_WARNING, + "Failed to copy export file '%s' for profile '%s'\n%s", + file.c_str(), currentProfile.name.c_str(), + error.what()); + return; + } + } } } -void OBSBasic::ChangeProfile() +// MARK: - Profile Management Helper Functions + +void OBSBasic::ActivateProfile(const OBSProfile &profile, bool reset) { - QAction *action = reinterpret_cast(sender()); ConfigFile config; - std::string path; + if (config.Open(profile.profileFile.u8string().c_str(), + CONFIG_OPEN_ALWAYS) != CONFIG_SUCCESS) { + throw std::logic_error( + "failed to open configuration file of new profile: " + + profile.profileFile.string()); + } - if (!action) - return; + config_set_string(config, "General", "Name", profile.name.c_str()); + config.SaveSafe("tmp"); - path = QT_TO_UTF8(action->property("file_name").value()); - if (path.empty()) - return; + std::vector restartRequirements; - const char *oldName = - config_get_string(App()->GlobalConfig(), "Basic", "Profile"); - if (action->text().compare(QT_UTF8(oldName)) == 0) { - action->setChecked(true); - return; - } + if (activeConfiguration) { + Auth::Save(); - size_t path_len = path.size(); - path += "/basic.ini"; + if (reset) { + auth.reset(); + DestroyPanelCookieManager(); +#ifdef YOUTUBE_ENABLED + if (youtubeAppDock) { + DeleteYouTubeAppDock(); + } +#endif + } + restartRequirements = GetRestartRequirements(config); - if (config.Open(path.c_str(), CONFIG_OPEN_ALWAYS) != 0) { - blog(LOG_ERROR, "ChangeProfile: Failed to load file '%s'", - path.c_str()); - return; + activeConfiguration.SaveSafe("tmp"); } - OnEvent(OBS_FRONTEND_EVENT_PROFILE_CHANGING); + activeConfiguration.Swap(config); - path.resize(path_len); + config_set_string(App()->GetUserConfig(), "Basic", "Profile", + profile.name.c_str()); + config_set_string(App()->GetUserConfig(), "Basic", "ProfileDir", + profile.directoryName.c_str()); - const char *newName = config_get_string(config, "General", "Name"); - const char *newDir = strrchr(path.c_str(), '/') + 1; + config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - QString settingsRequiringRestart; - bool needsRestart = - ProfileNeedsRestart(config, settingsRequiringRestart); + InitBasicConfigDefaults(); + InitBasicConfigDefaults2(); - config_set_string(App()->GlobalConfig(), "Basic", "Profile", newName); - config_set_string(App()->GlobalConfig(), "Basic", "ProfileDir", newDir); + if (reset) { + ResetProfileData(); + } - Auth::Save(); - auth.reset(); - DestroyPanelCookieManager(); -#ifdef YOUTUBE_ENABLED - if (youtubeAppDock) - DeleteYouTubeAppDock(); -#endif + CheckForSimpleModeX264Fallback(); - config.Swap(basicConfig); - InitBasicConfigDefaults(); - InitBasicConfigDefaults2(); - ResetProfileData(); RefreshProfiles(); - config_save_safe(App()->GlobalConfig(), "tmp", nullptr); + UpdateTitleBar(); UpdateVolumeControlsDecayRate(); Auth::Load(); -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected() && !youtubeAppDock) - NewYouTubeAppDock(); -#endif - - CheckForSimpleModeX264Fallback(); - - blog(LOG_INFO, "Switched to profile '%s' (%s)", newName, newDir); - blog(LOG_INFO, "------------------------------------------------"); OnEvent(OBS_FRONTEND_EVENT_PROFILE_CHANGED); - if (needsRestart) { + if (!restartRequirements.empty()) { + std::string requirements = std::accumulate( + std::next(restartRequirements.begin()), + restartRequirements.end(), restartRequirements[0], + [](std::string a, std::string b) { + return std::move(a) + "\n" + b; + }); + QMessageBox::StandardButton button = OBSMessageBox::question( this, QTStr("Restart"), QTStr("LoadProfileNeedsRestart") - .arg(settingsRequiringRestart)); + .arg(requirements.c_str())); if (button == QMessageBox::Yes) { restart = true; @@ -820,12 +800,67 @@ void OBSBasic::ChangeProfile() } } +void OBSBasic::ResetProfileData() +{ + ResetVideo(); + service = nullptr; + InitService(); + ResetOutputs(); + ClearHotkeys(); + CreateHotkeys(); + + /* load audio monitoring */ + if (obs_audio_monitoring_available()) { + const char *device_name = config_get_string( + activeConfiguration, "Audio", "MonitoringDeviceName"); + const char *device_id = config_get_string( + activeConfiguration, "Audio", "MonitoringDeviceId"); + + obs_set_audio_monitoring_device(device_name, device_id); + + blog(LOG_INFO, "Audio monitoring device:\n\tname: %s\n\tid: %s", + device_name, device_id); + } +} + +std::vector +OBSBasic::GetRestartRequirements(const ConfigFile &config) const +{ + std::vector result; + + const char *oldSpeakers = + config_get_string(activeConfiguration, "Audio", "ChannelSetup"); + const char *newSpeakers = + config_get_string(config, "Audio", "ChannelSetup"); + + uint64_t oldSampleRate = + config_get_uint(activeConfiguration, "Audio", "SampleRate"); + uint64_t newSampleRate = config_get_uint(config, "Audio", "SampleRate"); + + if (oldSpeakers != NULL && newSpeakers != NULL) { + if (std::string_view{oldSpeakers} != + std::string_view{newSpeakers}) { + result.emplace_back( + Str("Basic.Settings.Audio.Channels")); + } + } + + if (oldSampleRate != 0 && newSampleRate != 0) { + if (oldSampleRate != newSampleRate) { + result.emplace_back( + Str("Basic.Settings.Audio.SampleRate")); + } + } + + return result; +} + void OBSBasic::CheckForSimpleModeX264Fallback() { - const char *curStreamEncoder = - config_get_string(basicConfig, "SimpleOutput", "StreamEncoder"); - const char *curRecEncoder = - config_get_string(basicConfig, "SimpleOutput", "RecEncoder"); + const char *curStreamEncoder = config_get_string( + activeConfiguration, "SimpleOutput", "StreamEncoder"); + const char *curRecEncoder = config_get_string( + activeConfiguration, "SimpleOutput", "RecEncoder"); bool qsv_supported = false; bool qsv_av1_supported = false; bool amd_supported = false; @@ -941,11 +976,12 @@ void OBSBasic::CheckForSimpleModeX264Fallback() }; if (!CheckEncoder(curStreamEncoder)) - config_set_string(basicConfig, "SimpleOutput", "StreamEncoder", - curStreamEncoder); + config_set_string(activeConfiguration, "SimpleOutput", + "StreamEncoder", curStreamEncoder); if (!CheckEncoder(curRecEncoder)) - config_set_string(basicConfig, "SimpleOutput", "RecEncoder", - curRecEncoder); - if (changed) - config_save_safe(basicConfig, "tmp", nullptr); + config_set_string(activeConfiguration, "SimpleOutput", + "RecEncoder", curRecEncoder); + if (changed) { + activeConfiguration.SaveSafe("tmp"); + } } diff --git a/UI/window-basic-main-scene-collections.cpp b/UI/window-basic-main-scene-collections.cpp index a1ce86cbb8ad0d..4de7c29d4ccb4a 100644 --- a/UI/window-basic-main-scene-collections.cpp +++ b/UI/window-basic-main-scene-collections.cpp @@ -15,6 +15,9 @@ along with this program. If not, see . ******************************************************************************/ +#include +#include + #include #include #include @@ -27,209 +30,319 @@ #include "window-importer.hpp" #include "window-namedialog.hpp" -using namespace std; +// MARK: Constant Expressions + +constexpr std::string_view OBSSceneCollectionPath = "/obs-studio/basic/scenes/"; -void EnumSceneCollections(std::function &&cb) +// MARK: - Main Scene Collection Management Functions + +void OBSBasic::SetupNewSceneCollection(const std::string &collectionName) { - char path[512]; - os_glob_t *glob; - - int ret = GetConfigPath(path, sizeof(path), - "obs-studio/basic/scenes/*.json"); - if (ret <= 0) { - blog(LOG_WARNING, "Failed to get config path for scene " - "collections"); - return; - } + const OBSSceneCollection &newCollection = + CreateSceneCollection(collectionName); - if (os_glob(path, 0, &glob) != 0) { - blog(LOG_WARNING, "Failed to glob scene collections"); - return; + OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING); + + ActivateSceneCollection(newCollection); + + blog(LOG_INFO, "Created scene collection '%s' (clean, %s)", + newCollection.name.c_str(), newCollection.fileName.c_str()); + blog(LOG_INFO, "------------------------------------------------"); +} + +void OBSBasic::SetupDuplicateSceneCollection(const std::string &collectionName) +{ + const OBSSceneCollection &newCollection = + CreateSceneCollection(collectionName); + const OBSSceneCollection ¤tCollection = + GetCurrentSceneCollection(); + + SaveProjectNow(); + + const auto copyOptions = + std::filesystem::copy_options::overwrite_existing; + + try { + std::filesystem::copy(currentCollection.collectionFile, + newCollection.collectionFile, + copyOptions); + } catch (const std::filesystem::filesystem_error &error) { + blog(LOG_DEBUG, "%s", error.what()); + throw std::logic_error( + "Failed to copy file for cloned scene collection: " + + newCollection.name); } - for (size_t i = 0; i < glob->gl_pathc; i++) { - const char *filePath = glob->gl_pathv[i].path; + OBSDataAutoRelease collection = obs_data_create_from_json_file( + newCollection.collectionFile.u8string().c_str()); - if (glob->gl_pathv[i].directory) - continue; + obs_data_set_string(collection, "name", newCollection.name.c_str()); - OBSDataAutoRelease data = - obs_data_create_from_json_file_safe(filePath, "bak"); - std::string name = obs_data_get_string(data, "name"); + OBSDataArrayAutoRelease sources = + obs_data_get_array(collection, "sources"); - /* if no name found, use the file name as the name - * (this only happens when switching to the new version) */ - if (name.empty()) { - name = strrchr(filePath, '/') + 1; - name.resize(name.size() - 5); - } + if (sources) { + obs_data_erase(collection, "sources"); + + obs_data_array_enum( + sources, + [](obs_data_t *data, void *) -> void { + const char *uuid = os_generate_uuid(); - if (!cb(name.c_str(), filePath)) - break; + obs_data_set_string(data, "uuid", uuid); + + bfree((void *)uuid); + }, + nullptr); + + obs_data_set_array(collection, "sources", sources); } - os_globfree(glob); + obs_data_save_json_safe(collection, + newCollection.collectionFile.u8string().c_str(), + "tmp", nullptr); + + ActivateSceneCollection(newCollection); + + blog(LOG_INFO, "Created scene collection '%s' (duplicate, %s)", + newCollection.name.c_str(), newCollection.fileName.c_str()); + blog(LOG_INFO, "------------------------------------------------"); } -bool SceneCollectionExists(const char *findName) +void OBSBasic::SetupRenameSceneCollection(const std::string &collectionName) { - bool found = false; - auto func = [&](const char *name, const char *) { - if (strcmp(name, findName) == 0) { - found = true; - return false; - } + const OBSSceneCollection &newCollection = + CreateSceneCollection(collectionName); + const OBSSceneCollection currentCollection = + GetCurrentSceneCollection(); - return true; - }; + SaveProjectNow(); + + const auto copyOptions = + std::filesystem::copy_options::overwrite_existing; + + try { + std::filesystem::copy(currentCollection.collectionFile, + newCollection.collectionFile, + copyOptions); + } catch (const std::filesystem::filesystem_error &error) { + blog(LOG_DEBUG, "%s", error.what()); + throw std::logic_error( + "Failed to copy file for scene collection: " + + currentCollection.name); + } + + collections.erase(currentCollection.name); + + OBSDataAutoRelease collection = obs_data_create_from_json_file( + newCollection.collectionFile.u8string().c_str()); + + obs_data_set_string(collection, "name", newCollection.name.c_str()); - EnumSceneCollections(func); - return found; + obs_data_save_json_safe(collection, + newCollection.collectionFile.u8string().c_str(), + "tmp", nullptr); + + ActivateSceneCollection(newCollection); + RemoveSceneCollection(currentCollection); + + blog(LOG_INFO, "Renamed scene collection '%s' to '%s' (%s)", + currentCollection.name.c_str(), newCollection.name.c_str(), + newCollection.fileName.c_str()); + blog(LOG_INFO, "------------------------------------------------"); + + OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_RENAMED); } -static bool GetSceneCollectionName(QWidget *parent, std::string &name, - std::string &file, - const char *oldName = nullptr) +// MARK: - Scene Collection File Management Functions + +const OBSSceneCollection & +OBSBasic::CreateSceneCollection(const std::string &collectionName) { - bool rename = oldName != nullptr; - const char *title; - const char *text; + if (const auto &foundCollection = + GetSceneCollectionByName(collectionName)) { + throw std::invalid_argument( + "Scene collection already exists: " + collectionName); + } - if (rename) { - title = Str("Basic.Main.RenameSceneCollection.Title"); - text = Str("Basic.Main.AddSceneCollection.Text"); - } else { - title = Str("Basic.Main.AddSceneCollection.Title"); - text = Str("Basic.Main.AddSceneCollection.Text"); + std::string fileName; + if (!GetFileSafeName(collectionName.c_str(), fileName)) { + throw std::invalid_argument( + "Failed to create safe directory for new scene collection: " + + collectionName); } - for (;;) { - bool success = NameDialog::AskForName(parent, title, text, name, - QT_UTF8(oldName)); - if (!success) { - return false; - } - if (name.empty()) { - OBSMessageBox::warning(parent, - QTStr("NoNameEntered.Title"), - QTStr("NoNameEntered.Text")); - continue; - } - if (SceneCollectionExists(name.c_str())) { - OBSMessageBox::warning(parent, - QTStr("NameExists.Title"), - QTStr("NameExists.Text")); - continue; - } - break; + std::string collectionFile; + collectionFile.reserve(App()->userScenesLocation.u8string().size() + + OBSSceneCollectionPath.size() + fileName.size()); + collectionFile.append(App()->userScenesLocation.u8string()) + .append(OBSSceneCollectionPath) + .append(fileName); + + if (!GetClosestUnusedFileName(collectionFile, "json")) { + throw std::invalid_argument( + "Failed to get closest file name for new scene collection: " + + fileName); } - if (!GetUnusedSceneCollectionFile(name, file)) { - return false; + const std::filesystem::path collectionFilePath = + std::filesystem::u8path(collectionFile); + + auto [iterator, success] = collections.try_emplace( + collectionName, + OBSSceneCollection{collectionName, + collectionFilePath.filename().u8string(), + collectionFilePath}); + + return iterator->second; +} + +void OBSBasic::RemoveSceneCollection(OBSSceneCollection collection) +{ + std::filesystem::path collectionBackupFile{collection.collectionFile}; + collectionBackupFile.replace_extension("json.bak"); + + try { + std::filesystem::remove(collection.collectionFile); + std::filesystem::remove(collectionBackupFile); + } catch (const std::filesystem::filesystem_error &error) { + blog(LOG_DEBUG, "%s", error.what()); + throw std::logic_error( + "Failed to remove scene collection file: " + + collection.fileName); } - return true; + blog(LOG_INFO, "Removed scene collection '%s' (%s)", + collection.name.c_str(), collection.fileName.c_str()); + blog(LOG_INFO, "------------------------------------------------"); +} + +// MARK: - Scene Collection UI Handling Functions + +bool OBSBasic::CreateNewSceneCollection(const QString &name) +{ + try { + SetupNewSceneCollection(name.toStdString()); + return true; + } catch (const std::invalid_argument &error) { + blog(LOG_ERROR, "%s", error.what()); + return false; + } catch (const std::logic_error &error) { + blog(LOG_ERROR, "%s", error.what()); + return false; + } } -bool OBSBasic::AddSceneCollection(bool create_new, const QString &qname) +bool OBSBasic::CreateDuplicateSceneCollection(const QString &name) { - std::string name; - std::string file; + try { + SetupDuplicateSceneCollection(name.toStdString()); + return true; + } catch (const std::invalid_argument &error) { + blog(LOG_ERROR, "%s", error.what()); + return false; + } catch (const std::logic_error &error) { + blog(LOG_ERROR, "%s", error.what()); + return false; + } +} - if (qname.isEmpty()) { - if (!GetSceneCollectionName(this, name, file)) - return false; - } else { - name = QT_TO_UTF8(qname); - if (SceneCollectionExists(name.c_str())) - return false; +void OBSBasic::DeleteSceneCollection(const QString &name) +{ + const std::string_view currentCollectionName{config_get_string( + App()->GetUserConfig(), "Basic", "SceneCollection")}; - if (!GetUnusedSceneCollectionFile(name, file)) { - return false; - } + if (currentCollectionName == name.toStdString()) { + on_actionRemoveSceneCollection_triggered(); + return; } - auto new_collection = [this, create_new](const std::string &file, - const std::string &name) { - SaveProjectNow(); + OBSSceneCollection currentCollection = GetCurrentSceneCollection(); - config_set_string(App()->GlobalConfig(), "Basic", - "SceneCollection", name.c_str()); - config_set_string(App()->GlobalConfig(), "Basic", - "SceneCollectionFile", file.c_str()); + RemoveSceneCollection(currentCollection); - if (create_new) { - CreateDefaultScene(false); - } else { - obs_reset_source_uuids(); - } + collections.erase(name.toStdString()); - SaveProjectNow(); - RefreshSceneCollections(); - }; + RefreshSceneCollections(); - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING); + OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); +} - new_collection(file, name); +void OBSBasic::ChangeSceneCollection() +{ + QAction *action = reinterpret_cast(sender()); - blog(LOG_INFO, "Added scene collection '%s' (%s, %s.json)", - name.c_str(), create_new ? "clean" : "duplicate", file.c_str()); - blog(LOG_INFO, "------------------------------------------------"); + if (!action) { + return; + } - UpdateTitleBar(); + const std::string_view currentCollectionName{config_get_string( + App()->GetUserConfig(), "Basic", "SceneCollection")}; + const std::string selectedCollectionName{action->text().toStdString()}; - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); + if (currentCollectionName == selectedCollectionName) { + action->setChecked(true); + return; + } + + const std::optional foundCollection = + GetSceneCollectionByName(selectedCollectionName); + + if (!foundCollection) { + const std::string errorMessage{ + "Selected scene collection not found: "}; + + throw std::invalid_argument(errorMessage + + currentCollectionName.data()); + } + + const OBSSceneCollection &selectedCollection = foundCollection.value(); + + OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING); + + ActivateSceneCollection(selectedCollection); - return true; + blog(LOG_INFO, "Switched to scene collection '%s' (%s)", + selectedCollection.name.c_str(), + selectedCollection.fileName.c_str()); + blog(LOG_INFO, "------------------------------------------------"); } -void OBSBasic::RefreshSceneCollections() +void OBSBasic::RefreshSceneCollections(bool refreshCache) { + std::string_view currentCollectionName{config_get_string( + App()->GetUserConfig(), "Basic", "SceneCollection")}; + QList menuActions = ui->sceneCollectionMenu->actions(); - int count = 0; - for (int i = 0; i < menuActions.count(); i++) { - QVariant v = menuActions[i]->property("file_name"); - if (v.typeName() != nullptr) - delete menuActions[i]; + for (auto &action : menuActions) { + QVariant variant = action->property("file_name"); + if (variant.typeName() != nullptr) { + delete action; + } } - const char *cur_name = config_get_string(App()->GlobalConfig(), "Basic", - "SceneCollection"); - - auto addCollection = [&](const char *name, const char *path) { - std::string file = strrchr(path, '/') + 1; - file.erase(file.size() - 5, 5); + if (refreshCache) { + RefreshSceneCollectionCache(); + } - QAction *action = new QAction(QT_UTF8(name), this); - action->setProperty("file_name", QT_UTF8(path)); + size_t numAddedCollections = 0; + for (auto &[collectionName, collection] : collections) { + QAction *action = new QAction( + QString().fromStdString(collectionName), this); + action->setProperty("file_name", QString().fromStdString( + collection.fileName)); connect(action, &QAction::triggered, this, &OBSBasic::ChangeSceneCollection); action->setCheckable(true); - - action->setChecked(strcmp(name, cur_name) == 0); + action->setChecked(collectionName == currentCollectionName); ui->sceneCollectionMenu->addAction(action); - count++; - return true; - }; - EnumSceneCollections(addCollection); - - /* force saving of first scene collection on first run, otherwise - * no scene collections will show up */ - if (!count) { - long prevDisableVal = disableSaving; - - disableSaving = 0; - SaveProjectNow(); - disableSaving = prevDisableVal; - - EnumSceneCollections(addCollection); + numAddedCollections += 1; } - ui->actionRemoveSceneCollection->setEnabled(count > 1); + ui->actionRemoveSceneCollection->setEnabled(numAddedCollections > 1); OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); @@ -238,125 +351,243 @@ void OBSBasic::RefreshSceneCollections() main->ui->actionPasteDup->setEnabled(false); } -void OBSBasic::on_actionNewSceneCollection_triggered() +// MARK: - Scene Collection Cache Functions + +void OBSBasic::RefreshSceneCollectionCache() +{ + OBSSceneCollectionCache foundCollections{}; + + const std::filesystem::path collectionsPath = + App()->userScenesLocation / + std::filesystem::u8path(OBSSceneCollectionPath.substr(1)); + + if (!std::filesystem::exists(collectionsPath)) { + blog(LOG_WARNING, + "Failed to get scene collections config path"); + return; + } + + for (const auto &entry : + std::filesystem::directory_iterator(collectionsPath)) { + if (entry.is_directory()) { + continue; + } + + if (entry.path().extension().u8string() != ".json") { + continue; + } + + OBSDataAutoRelease collectionData = + obs_data_create_from_json_file_safe( + entry.path().u8string().c_str(), "bak"); + + std::string candidateName; + const char *collectionName = + obs_data_get_string(collectionData, "name"); + + if (!collectionName) { + candidateName = entry.path().filename().u8string(); + } else { + candidateName = collectionName; + } + + foundCollections.try_emplace( + candidateName, + OBSSceneCollection{candidateName, + entry.path().filename().u8string(), + entry.path()}); + } + + collections.swap(foundCollections); +} + +const OBSSceneCollection &OBSBasic::GetCurrentSceneCollection() const { - AddSceneCollection(true); + std::string currentCollectionName{config_get_string( + App()->GetUserConfig(), "Basic", "SceneCollection")}; + + if (currentCollectionName.empty()) { + throw std::invalid_argument( + "No valid scene collection name in configuration Basic->SceneCollection"); + } + + const auto &foundCollection = collections.find(currentCollectionName); + + if (foundCollection != collections.end()) { + return foundCollection->second; + } else { + throw std::invalid_argument( + "Scene collection not found in collection list: " + + currentCollectionName); + } } -void OBSBasic::on_actionDupSceneCollection_triggered() +std::optional +OBSBasic::GetSceneCollectionByName(const std::string &collectionName) const { - AddSceneCollection(false); + auto foundCollection = collections.find(collectionName); + + if (foundCollection == collections.end()) { + return {}; + } else { + return foundCollection->second; + } } -void OBSBasic::on_actionRenameSceneCollection_triggered() +std::optional +OBSBasic::GetSceneCollectionByFileName(const std::string &fileName) const { - std::string name; - std::string file; - std::string oname; - - std::string oldFile = config_get_string(App()->GlobalConfig(), "Basic", - "SceneCollectionFile"); - const char *oldName = config_get_string(App()->GlobalConfig(), "Basic", - "SceneCollection"); - oname = std::string(oldName); - - bool success = GetSceneCollectionName(this, name, file, oldName); - if (!success) - return; + for (auto &[iterator, collection] : collections) { + if (collection.fileName == fileName) { + return collection; + } + } - config_set_string(App()->GlobalConfig(), "Basic", "SceneCollection", - name.c_str()); - config_set_string(App()->GlobalConfig(), "Basic", "SceneCollectionFile", - file.c_str()); - SaveProjectNow(); + return {}; +} + +// MARK: - Qt Slot Functions - char path[512]; - int ret = GetConfigPath(path, 512, "obs-studio/basic/scenes/"); - if (ret <= 0) { - blog(LOG_WARNING, "Failed to get scene collection config path"); +void OBSBasic::on_actionNewSceneCollection_triggered() +{ + const OBSPromptCallback sceneCollectionCallback = + [this](const OBSPromptResult &result) { + if (GetSceneCollectionByName(result.promptValue)) { + return false; + } + + return true; + }; + + const OBSPromptRequest request{ + Str("Basic.Main.AddSceneCollection.Title"), + Str("Basic.Main.AddSceneCollection.Text")}; + + OBSPromptResult result = + PromptForName(request, sceneCollectionCallback); + + if (!result.success) { return; } - oldFile.insert(0, path); - oldFile += ".json"; - os_unlink(oldFile.c_str()); - oldFile += ".bak"; - os_unlink(oldFile.c_str()); + try { + SetupNewSceneCollection(result.promptValue); + } catch (const std::invalid_argument &error) { + blog(LOG_ERROR, "%s", error.what()); + } catch (const std::logic_error &error) { + blog(LOG_ERROR, "%s", error.what()); + } +} + +void OBSBasic::on_actionDupSceneCollection_triggered() +{ + const OBSPromptCallback sceneCollectionCallback = + [this](const OBSPromptResult &result) { + if (GetSceneCollectionByName(result.promptValue)) { + return false; + } - blog(LOG_INFO, "------------------------------------------------"); - blog(LOG_INFO, "Renamed scene collection to '%s' (%s.json)", - name.c_str(), file.c_str()); - blog(LOG_INFO, "------------------------------------------------"); + return true; + }; - UpdateTitleBar(); - RefreshSceneCollections(); + const OBSPromptRequest request{ + Str("Basic.Main.AddSceneCollection.Title"), + Str("Basic.Main.AddSceneCollection.Text")}; - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_RENAMED); + OBSPromptResult result = + PromptForName(request, sceneCollectionCallback); + + if (!result.success) { + return; + } + + try { + SetupDuplicateSceneCollection(result.promptValue); + } catch (const std::invalid_argument &error) { + blog(LOG_ERROR, "%s", error.what()); + } catch (const std::logic_error &error) { + blog(LOG_ERROR, "%s", error.what()); + } } -void OBSBasic::on_actionRemoveSceneCollection_triggered() +void OBSBasic::on_actionRenameSceneCollection_triggered() { - std::string newName; - std::string newPath; - - std::string oldFile = config_get_string(App()->GlobalConfig(), "Basic", - "SceneCollectionFile"); - std::string oldName = config_get_string(App()->GlobalConfig(), "Basic", - "SceneCollection"); - - auto cb = [&](const char *name, const char *filePath) { - if (strcmp(oldName.c_str(), name) != 0) { - newName = name; - newPath = filePath; - return false; - } + const OBSSceneCollection ¤tCollection = + GetCurrentSceneCollection(); - return true; - }; + const OBSPromptCallback sceneCollectionCallback = + [this](const OBSPromptResult &result) { + if (GetSceneCollectionByName(result.promptValue)) { + return false; + } - EnumSceneCollections(cb); + return true; + }; - /* this should never be true due to menu item being grayed out */ - if (newPath.empty()) - return; + const OBSPromptRequest request{ + Str("Basic.Main.RenameSceneCollection.Title"), + Str("Basic.Main.AddSceneCollection.Text"), + currentCollection.name}; - QString text = - QTStr("ConfirmRemove.Text").arg(QT_UTF8(oldName.c_str())); + OBSPromptResult result = + PromptForName(request, sceneCollectionCallback); - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmRemove.Title"), text); - if (button == QMessageBox::No) + if (!result.success) { return; + } + + try { + SetupRenameSceneCollection(result.promptValue); + } catch (const std::invalid_argument &error) { + blog(LOG_ERROR, "%s", error.what()); + } catch (const std::logic_error &error) { + blog(LOG_ERROR, "%s", error.what()); + } +} - char path[512]; - int ret = GetConfigPath(path, 512, "obs-studio/basic/scenes/"); - if (ret <= 0) { - blog(LOG_WARNING, "Failed to get scene collection config path"); +void OBSBasic::on_actionRemoveSceneCollection_triggered(bool skipConfirmation) +{ + if (collections.size() < 2) { return; } - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING); + OBSSceneCollection currentCollection; - oldFile.insert(0, path); - /* os_rename() overwrites if necessary, only the .bak file will remain. */ - os_rename((oldFile + ".json").c_str(), (oldFile + ".json.bak").c_str()); + try { + currentCollection = GetCurrentSceneCollection(); - Load(newPath.c_str()); - RefreshSceneCollections(); + if (!skipConfirmation) { + const QString confirmationText = + QTStr("ConfirmRemove.Text") + .arg(QString::fromStdString( + currentCollection.name)); + const QMessageBox::StandardButton button = + OBSMessageBox::question( + this, QTStr("ConfirmRemove.Title"), + confirmationText); - const char *newFile = config_get_string(App()->GlobalConfig(), "Basic", - "SceneCollectionFile"); + if (button == QMessageBox::No) { + return; + } + } - blog(LOG_INFO, - "Removed scene collection '%s' (%s.json), " - "switched to '%s' (%s.json)", - oldName.c_str(), oldFile.c_str(), newName.c_str(), newFile); - blog(LOG_INFO, "------------------------------------------------"); + OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING); - UpdateTitleBar(); + collections.erase(currentCollection.name); + } catch (const std::invalid_argument &error) { + blog(LOG_ERROR, "%s", error.what()); + } catch (const std::logic_error &error) { + blog(LOG_ERROR, "%s", error.what()); + } - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); + const OBSSceneCollection &newCollection = collections.rbegin()->second; + + ActivateSceneCollection(newCollection); + RemoveSceneCollection(currentCollection); + + blog(LOG_INFO, "Switched to scene collection '%s' (%s)", + newCollection.name.c_str(), newCollection.fileName.c_str()); + blog(LOG_INFO, "------------------------------------------------"); } void OBSBasic::on_actionImportSceneCollection_triggered() @@ -364,37 +595,32 @@ void OBSBasic::on_actionImportSceneCollection_triggered() OBSImporter imp(this); imp.exec(); - RefreshSceneCollections(); + RefreshSceneCollections(true); } void OBSBasic::on_actionExportSceneCollection_triggered() { SaveProjectNow(); - char path[512]; - - QString home = QDir::homePath(); + const OBSSceneCollection ¤tCollection = + GetCurrentSceneCollection(); - QString currentFile = QT_UTF8(config_get_string( - App()->GlobalConfig(), "Basic", "SceneCollectionFile")); + const QString home = QDir::homePath(); - int ret = GetConfigPath(path, 512, "obs-studio/basic/scenes/"); - if (ret <= 0) { - blog(LOG_WARNING, "Failed to get scene collection config path"); - return; - } - - QString exportFile = + const QString destinationFileName = SaveFile(this, QTStr("Basic.MainMenu.SceneCollection.Export"), - home + "/" + currentFile, "JSON Files (*.json)"); - - string file = QT_TO_UTF8(exportFile); + home + "/" + currentCollection.fileName.c_str(), + "JSON Files (*.json)"); - if (!exportFile.isEmpty() && !exportFile.isNull()) { - QString inputFile = path + currentFile + ".json"; + if (!destinationFileName.isEmpty() && !destinationFileName.isNull()) { + const std::filesystem::path sourceFile = + currentCollection.collectionFile; + const std::filesystem::path destinationFile = + std::filesystem::u8path( + destinationFileName.toStdString()); - OBSDataAutoRelease collection = - obs_data_create_from_json_file(QT_TO_UTF8(inputFile)); + OBSDataAutoRelease collection = obs_data_create_from_json_file( + sourceFile.u8string().c_str()); OBSDataArrayAutoRelease sources = obs_data_get_array(collection, "sources"); @@ -403,15 +629,17 @@ void OBSBasic::on_actionExportSceneCollection_triggered() "No sources in exported scene collection"); return; } + obs_data_erase(collection, "sources"); - // We're just using std::sort on a vector to make life easier. - vector sourceItems; + using OBSDataVector = std::vector; + + OBSDataVector sourceItems; obs_data_array_enum( sources, - [](obs_data_t *data, void *pVec) -> void { - auto &sourceItems = - *static_cast *>(pVec); + [](obs_data_t *data, void *vector) -> void { + OBSDataVector &sourceItems{ + *static_cast(vector)}; sourceItems.push_back(data); }, &sourceItems); @@ -425,12 +653,14 @@ void OBSBasic::on_actionExportSceneCollection_triggered() }); OBSDataArrayAutoRelease newSources = obs_data_array_create(); - for (auto &item : sourceItems) + for (auto &item : sourceItems) { obs_data_array_push_back(newSources, item); + } obs_data_set_array(collection, "sources", newSources); obs_data_save_json_pretty_safe( - collection, QT_TO_UTF8(exportFile), "tmp", "bak"); + collection, destinationFile.u8string().c_str(), "tmp", + "bak"); } } @@ -467,8 +697,10 @@ void OBSBasic::on_actionRemigrateSceneCollection_triggered() return; } - QString name = config_get_string(App()->GlobalConfig(), "Basic", - "SceneCollection"); + const OBSSceneCollection ¤tCollection = + GetCurrentSceneCollection(); + + QString name = QString::fromStdString(currentCollection.name); QString message = QTStr("Basic.Main.RemigrateSceneCollection.Text") .arg(name) .arg(ovi.base_width) @@ -497,71 +729,43 @@ void OBSBasic::on_actionRemigrateSceneCollection_triggered() } } - char path[512]; - int ret = GetConfigPath(path, 512, "obs-studio/basic/scenes/"); - if (ret <= 0) { - blog(LOG_WARNING, "Failed to get scene collection config path"); - return; - } - - std::string fileName = path; - fileName += config_get_string(App()->GlobalConfig(), "Basic", - "SceneCollectionFile"); - fileName += ".json"; - - if (api) - api->on_event(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING); + OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING); /* Save and immediately reload to (re-)run migrations. */ SaveProjectNow(); /* Reset video if we potentially changed to a temporary resolution */ - if (!usingAbsoluteCoordinates) + if (!usingAbsoluteCoordinates) { ResetVideo(); + } - Load(fileName.c_str(), !usingAbsoluteCoordinates); - RefreshSceneCollections(); - - if (api) - api->on_event(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); + ActivateSceneCollection(currentCollection); } -void OBSBasic::ChangeSceneCollection() -{ - QAction *action = reinterpret_cast(sender()); - std::string fileName; - - if (!action) - return; - - fileName = QT_TO_UTF8(action->property("file_name").value()); - if (fileName.empty()) - return; +// MARK: - Scene Collection Management Helper Functions - const char *oldName = config_get_string(App()->GlobalConfig(), "Basic", - "SceneCollection"); +void OBSBasic::ActivateSceneCollection(const OBSSceneCollection &collection) +{ + const std::string currentCollectionName{config_get_string( + App()->GetUserConfig(), "Basic", "SceneCollection")}; - if (action->text().compare(QT_UTF8(oldName)) == 0) { - action->setChecked(true); - return; + if (auto foundCollection = + GetSceneCollectionByName(currentCollectionName)) { + if (collection.name != foundCollection.value().name) { + SaveProjectNow(); + } } - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING); + config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", + collection.name.c_str()); + config_set_string(App()->GetUserConfig(), "Basic", + "SceneCollectionFile", collection.fileName.c_str()); - SaveProjectNow(); + Load(collection.collectionFile.u8string().c_str()); - Load(fileName.c_str()); RefreshSceneCollections(); - const char *newName = config_get_string(App()->GlobalConfig(), "Basic", - "SceneCollection"); - const char *newFile = config_get_string(App()->GlobalConfig(), "Basic", - "SceneCollectionFile"); - - blog(LOG_INFO, "Switched to scene collection '%s' (%s.json)", newName, - newFile); - blog(LOG_INFO, "------------------------------------------------"); - UpdateTitleBar(); + OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); } diff --git a/UI/window-basic-main-transitions.cpp b/UI/window-basic-main-transitions.cpp index 5a6a58a021935d..6b96aba44d6c7c 100644 --- a/UI/window-basic-main-transitions.cpp +++ b/UI/window-basic-main-transitions.cpp @@ -1839,10 +1839,11 @@ int OBSBasic::GetOverrideTransitionDuration(OBSSource source) void OBSBasic::UpdatePreviewProgramIndicators() { - bool labels = previewProgramMode ? config_get_bool(GetGlobalConfig(), - "BasicWindow", - "StudioModeLabels") - : false; + bool labels = previewProgramMode + ? config_get_bool(App()->GetUserConfig(), + "BasicWindow", + "StudioModeLabels") + : false; ui->previewLabel->setVisible(labels); diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index 3e0d9b17e46639..8c6e646fa2e726 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -162,6 +162,8 @@ template static void SetOBSRef(QListWidgetItem *item, T &&val) QVariant::fromValue(val)); } +constexpr std::string_view OBSProfilePath = "/obs-studio/basic/profiles/"; + static void AddExtraModulePaths() { string plugins_path, plugins_data_path; @@ -191,11 +193,11 @@ static void AddExtraModulePaths() int ret = GetProgramDataPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); #elif defined(__APPLE__) - int ret = GetConfigPath(base_module_dir, sizeof(base_module_dir), - "obs-studio/plugins/%module%.plugin"); + int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), + "obs-studio/plugins/%module%.plugin"); #else - int ret = GetConfigPath(base_module_dir, sizeof(base_module_dir), - "obs-studio/plugins/%module%"); + int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), + "obs-studio/plugins/%module%"); #endif if (ret <= 0) @@ -219,8 +221,8 @@ static void AddExtraModulePaths() /* Legacy User Application Support Search Path */ char user_legacy_module_dir[PATH_MAX]; - GetConfigPath(user_legacy_module_dir, sizeof(user_legacy_module_dir), - "obs-studio/plugins/%module%"); + GetAppConfigPath(user_legacy_module_dir, sizeof(user_legacy_module_dir), + "obs-studio/plugins/%module%"); std::string path_user_legacy = user_legacy_module_dir; obs_add_module_path((path_user_legacy + "/bin").c_str(), (path_user_legacy + "/data").c_str()); @@ -446,7 +448,7 @@ OBSBasic::OBSBasic(QWidget *parent) ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false); ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false); - bool sceneGrid = config_get_bool(App()->GlobalConfig(), "BasicWindow", + bool sceneGrid = config_get_bool(App()->GetUserConfig(), "BasicWindow", "gridMode"); ui->scenes->SetGridMode(sceneGrid); @@ -602,7 +604,7 @@ OBSBasic::OBSBasic(QWidget *parent) QPoint curPos; //restore parent window geometry - const char *geometry = config_get_string(App()->GlobalConfig(), + const char *geometry = config_get_string(App()->GetUserConfig(), "BasicWindow", "geometry"); if (geometry != NULL) { QByteArray byteArray = @@ -726,7 +728,7 @@ static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, const char *programName = obs_source_get_name(curProgramScene); const char *sceneCollection = config_get_string( - App()->GlobalConfig(), "Basic", "SceneCollection"); + App()->GetUserConfig(), "Basic", "SceneCollection"); obs_data_set_string(saveData, "current_scene", sceneName); obs_data_set_string(saveData, "current_program_scene", programName); @@ -786,8 +788,8 @@ void OBSBasic::copyActionsDynamicProperties() void OBSBasic::UpdateVolumeControlsDecayRate() { - double meterDecayRate = - config_get_double(basicConfig, "Audio", "MeterDecayRate"); + double meterDecayRate = config_get_double(activeConfiguration, "Audio", + "MeterDecayRate"); for (size_t i = 0; i < volumes.size(); i++) { volumes[i]->SetMeterDecayRate(meterDecayRate); @@ -797,7 +799,7 @@ void OBSBasic::UpdateVolumeControlsDecayRate() void OBSBasic::UpdateVolumeControlsPeakMeterType() { uint32_t peakMeterTypeIdx = - config_get_uint(basicConfig, "Audio", "PeakMeterType"); + config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); enum obs_peak_meter_type peakMeterType; switch (peakMeterTypeIdx) { @@ -1244,12 +1246,16 @@ void OBSBasic::Load(const char *file, bool remigrate) } } - config_set_string(App()->GlobalConfig(), "Basic", + config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", name.c_str()); - config_set_string(App()->GlobalConfig(), "Basic", + config_set_string(App()->GetUserConfig(), "Basic", "SceneCollectionFile", name.c_str()); blog(LOG_INFO, "No scene file found, creating default scene"); - CreateDefaultScene(true); + + bool hasFirstRun = config_get_bool(App()->GetUserConfig(), + "General", "FirstRun"); + + CreateDefaultScene(!hasFirstRun); SaveProject(); return; } @@ -1342,7 +1348,7 @@ void OBSBasic::LoadData(obs_data_t *data, const char *file, bool remigrate) transitionName = obs_source_get_name(fadeTransition); const char *curSceneCollection = config_get_string( - App()->GlobalConfig(), "Basic", "SceneCollection"); + App()->GetUserConfig(), "Basic", "SceneCollection"); obs_data_set_default_string(data, "name", curSceneCollection); @@ -1475,8 +1481,8 @@ void OBSBasic::LoadData(obs_data_t *data, const char *file, bool remigrate) /* ------------------- */ - bool projectorSave = config_get_bool(GetGlobalConfig(), "BasicWindow", - "SaveProjectors"); + bool projectorSave = config_get_bool(App()->GetUserConfig(), + "BasicWindow", "SaveProjectors"); if (projectorSave) { OBSDataArrayAutoRelease savedProjectors = @@ -1494,10 +1500,10 @@ void OBSBasic::LoadData(obs_data_t *data, const char *file, bool remigrate) std::string file_base = strrchr(file, '/') + 1; file_base.erase(file_base.size() - 5, 5); - config_set_string(App()->GlobalConfig(), "Basic", "SceneCollection", + config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", name); - config_set_string(App()->GlobalConfig(), "Basic", "SceneCollectionFile", - file_base.c_str()); + config_set_string(App()->GetUserConfig(), "Basic", + "SceneCollectionFile", file_base.c_str()); OBSDataArrayAutoRelease quickTransitionData = obs_data_get_array(data, "quick_transitions"); @@ -1612,18 +1618,18 @@ void OBSBasic::LoadData(obs_data_t *data, const char *file, bool remigrate) OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); } -#define SERVICE_PATH "service.json" +constexpr std::string_view OBSServiceFileName = "service.json"; void OBSBasic::SaveService() { if (!service) return; - char serviceJsonPath[512]; - int ret = GetProfilePath(serviceJsonPath, sizeof(serviceJsonPath), - SERVICE_PATH); - if (ret <= 0) - return; + const OBSProfile ¤tProfile = GetCurrentProfile(); + + const std::filesystem::path jsonFilePath = + currentProfile.path / + std::filesystem::u8path(OBSServiceFileName); OBSDataAutoRelease data = obs_data_create(); OBSDataAutoRelease settings = obs_service_get_settings(service); @@ -1631,26 +1637,35 @@ void OBSBasic::SaveService() obs_data_set_string(data, "type", obs_service_get_type(service)); obs_data_set_obj(data, "settings", settings); - if (!obs_data_save_json_safe(data, serviceJsonPath, "tmp", "bak")) + if (!obs_data_save_json_safe(data, jsonFilePath.u8string().c_str(), + "tmp", "bak")) { blog(LOG_WARNING, "Failed to save service"); + } } bool OBSBasic::LoadService() { - const char *type; + OBSDataAutoRelease data; - char serviceJsonPath[512]; - int ret = GetProfilePath(serviceJsonPath, sizeof(serviceJsonPath), - SERVICE_PATH); - if (ret <= 0) - return false; + try { + const OBSProfile ¤tProfile = GetCurrentProfile(); - OBSDataAutoRelease data = - obs_data_create_from_json_file_safe(serviceJsonPath, "bak"); + const std::filesystem::path jsonFilePath = + currentProfile.path / + std::filesystem::u8path(OBSServiceFileName); - if (!data) + data = obs_data_create_from_json_file_safe( + jsonFilePath.u8string().c_str(), "bak"); + + if (!data) { + return false; + } + } catch (const std::invalid_argument &error) { + blog(LOG_ERROR, "%s", error.what()); return false; + } + const char *type; obs_data_set_default_string(data, "type", "rtmp_common"); type = obs_data_get_string(data, "type"); @@ -1666,19 +1681,20 @@ bool OBSBasic::LoadService() /* Enforce Opus on WHIP if needed */ if (strcmp(obs_service_get_protocol(service), "WHIP") == 0) { - const char *option = config_get_string( - basicConfig, "SimpleOutput", "StreamAudioEncoder"); + const char *option = config_get_string(activeConfiguration, + "SimpleOutput", + "StreamAudioEncoder"); if (strcmp(option, "opus") != 0) - config_set_string(basicConfig, "SimpleOutput", + config_set_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "opus"); - option = config_get_string(basicConfig, "AdvOut", + option = config_get_string(activeConfiguration, "AdvOut", "AudioEncoder"); const char *encoder_codec = obs_get_encoder_codec(option); if (!encoder_codec || strcmp(encoder_codec, "opus") != 0) - config_set_string(basicConfig, "AdvOut", "AudioEncoder", - "ffmpeg_opus"); + config_set_string(activeConfiguration, "AdvOut", + "AudioEncoder", "ffmpeg_opus"); } return true; @@ -1733,7 +1749,7 @@ bool OBSBasic::InitBasicConfigDefaults() cy *= devicePixelRatioF(); bool oldResolutionDefaults = config_get_bool( - App()->GlobalConfig(), "General", "Pre19Defaults"); + App()->GetUserConfig(), "General", "Pre19Defaults"); /* use 1920x1080 for new default base res if main monitor is above * 1920x1080, but don't apply for people from older builds -- only to @@ -1747,62 +1763,74 @@ bool OBSBasic::InitBasicConfigDefaults() /* ----------------------------------------------------- */ /* move over old FFmpeg track settings */ - if (config_has_user_value(basicConfig, "AdvOut", "FFAudioTrack") && - !config_has_user_value(basicConfig, "AdvOut", "Pre22.1Settings")) { + if (config_has_user_value(activeConfiguration, "AdvOut", + "FFAudioTrack") && + !config_has_user_value(activeConfiguration, "AdvOut", + "Pre22.1Settings")) { - int track = (int)config_get_int(basicConfig, "AdvOut", + int track = (int)config_get_int(activeConfiguration, "AdvOut", "FFAudioTrack"); - config_set_int(basicConfig, "AdvOut", "FFAudioMixes", + config_set_int(activeConfiguration, "AdvOut", "FFAudioMixes", 1LL << (track - 1)); - config_set_bool(basicConfig, "AdvOut", "Pre22.1Settings", true); + config_set_bool(activeConfiguration, "AdvOut", + "Pre22.1Settings", true); changed = true; } /* ----------------------------------------------------- */ /* move over mixer values in advanced if older config */ - if (config_has_user_value(basicConfig, "AdvOut", "RecTrackIndex") && - !config_has_user_value(basicConfig, "AdvOut", "RecTracks")) { + if (config_has_user_value(activeConfiguration, "AdvOut", + "RecTrackIndex") && + !config_has_user_value(activeConfiguration, "AdvOut", + "RecTracks")) { - uint64_t track = - config_get_uint(basicConfig, "AdvOut", "RecTrackIndex"); + uint64_t track = config_get_uint(activeConfiguration, "AdvOut", + "RecTrackIndex"); track = 1ULL << (track - 1); - config_set_uint(basicConfig, "AdvOut", "RecTracks", track); - config_remove_value(basicConfig, "AdvOut", "RecTrackIndex"); + config_set_uint(activeConfiguration, "AdvOut", "RecTracks", + track); + config_remove_value(activeConfiguration, "AdvOut", + "RecTrackIndex"); changed = true; } /* ----------------------------------------------------- */ /* set twitch chat extensions to "both" if prev version */ /* is under 24.1 */ - if (config_get_bool(GetGlobalConfig(), "General", "Pre24.1Defaults") && - !config_has_user_value(basicConfig, "Twitch", "AddonChoice")) { - config_set_int(basicConfig, "Twitch", "AddonChoice", 3); + if (config_get_bool(App()->GetUserConfig(), "General", + "Pre24.1Defaults") && + !config_has_user_value(activeConfiguration, "Twitch", + "AddonChoice")) { + config_set_int(activeConfiguration, "Twitch", "AddonChoice", 3); changed = true; } /* ----------------------------------------------------- */ /* move bitrate enforcement setting to new value */ - if (config_has_user_value(basicConfig, "SimpleOutput", + if (config_has_user_value(activeConfiguration, "SimpleOutput", "EnforceBitrate") && - !config_has_user_value(basicConfig, "Stream1", + !config_has_user_value(activeConfiguration, "Stream1", "IgnoreRecommended") && - !config_has_user_value(basicConfig, "Stream1", "MovedOldEnforce")) { - bool enforce = config_get_bool(basicConfig, "SimpleOutput", - "EnforceBitrate"); - config_set_bool(basicConfig, "Stream1", "IgnoreRecommended", - !enforce); - config_set_bool(basicConfig, "Stream1", "MovedOldEnforce", - true); + !config_has_user_value(activeConfiguration, "Stream1", + "MovedOldEnforce")) { + bool enforce = config_get_bool( + activeConfiguration, "SimpleOutput", "EnforceBitrate"); + config_set_bool(activeConfiguration, "Stream1", + "IgnoreRecommended", !enforce); + config_set_bool(activeConfiguration, "Stream1", + "MovedOldEnforce", true); changed = true; } /* ----------------------------------------------------- */ /* enforce minimum retry delay of 1 second prior to 27.1 */ - if (config_has_user_value(basicConfig, "Output", "RetryDelay")) { - int retryDelay = - config_get_uint(basicConfig, "Output", "RetryDelay"); + if (config_has_user_value(activeConfiguration, "Output", + "RetryDelay")) { + int retryDelay = config_get_uint(activeConfiguration, "Output", + "RetryDelay"); if (retryDelay < 1) { - config_set_uint(basicConfig, "Output", "RetryDelay", 1); + config_set_uint(activeConfiguration, "Output", + "RetryDelay", 1); changed = true; } } @@ -1811,15 +1839,15 @@ bool OBSBasic::InitBasicConfigDefaults() /* Migrate old container selection (if any) to new key. */ auto MigrateFormat = [&](const char *section) { - bool has_old_key = config_has_user_value(basicConfig, section, - "RecFormat"); - bool has_new_key = config_has_user_value(basicConfig, section, - "RecFormat2"); + bool has_old_key = config_has_user_value(activeConfiguration, + section, "RecFormat"); + bool has_new_key = config_has_user_value(activeConfiguration, + section, "RecFormat2"); if (!has_new_key && !has_old_key) return; string old_format = config_get_string( - basicConfig, section, + activeConfiguration, section, has_new_key ? "RecFormat2" : "RecFormat"); string new_format = old_format; if (old_format == "ts") @@ -1832,8 +1860,8 @@ bool OBSBasic::InitBasicConfigDefaults() new_format = "fragmented_mov"; if (new_format != old_format || !has_new_key) { - config_set_string(basicConfig, section, "RecFormat2", - new_format.c_str()); + config_set_string(activeConfiguration, section, + "RecFormat2", new_format.c_str()); changed = true; } }; @@ -1844,137 +1872,175 @@ bool OBSBasic::InitBasicConfigDefaults() /* ----------------------------------------------------- */ /* Migrate output scale setting to GPU scaling options. */ - if (config_get_bool(basicConfig, "AdvOut", "Rescale") && - !config_has_user_value(basicConfig, "AdvOut", "RescaleFilter")) { - config_set_int(basicConfig, "AdvOut", "RescaleFilter", + if (config_get_bool(activeConfiguration, "AdvOut", "Rescale") && + !config_has_user_value(activeConfiguration, "AdvOut", + "RescaleFilter")) { + config_set_int(activeConfiguration, "AdvOut", "RescaleFilter", OBS_SCALE_BILINEAR); } - if (config_get_bool(basicConfig, "AdvOut", "RecRescale") && - !config_has_user_value(basicConfig, "AdvOut", "RecRescaleFilter")) { - config_set_int(basicConfig, "AdvOut", "RecRescaleFilter", - OBS_SCALE_BILINEAR); + if (config_get_bool(activeConfiguration, "AdvOut", "RecRescale") && + !config_has_user_value(activeConfiguration, "AdvOut", + "RecRescaleFilter")) { + config_set_int(activeConfiguration, "AdvOut", + "RecRescaleFilter", OBS_SCALE_BILINEAR); } /* ----------------------------------------------------- */ - if (changed) - config_save_safe(basicConfig, "tmp", nullptr); + if (changed) { + activeConfiguration.SaveSafe("tmp"); + } /* ----------------------------------------------------- */ - config_set_default_string(basicConfig, "Output", "Mode", "Simple"); + config_set_default_string(activeConfiguration, "Output", "Mode", + "Simple"); - config_set_default_bool(basicConfig, "Stream1", "IgnoreRecommended", - false); - config_set_default_bool(basicConfig, "Stream1", "EnableMultitrackVideo", - false); - config_set_default_bool(basicConfig, "Stream1", + config_set_default_bool(activeConfiguration, "Stream1", + "IgnoreRecommended", false); + config_set_default_bool(activeConfiguration, "Stream1", + "EnableMultitrackVideo", false); + config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto", true); - config_set_default_bool(basicConfig, "Stream1", + config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumVideoTracksAuto", true); - config_set_default_string(basicConfig, "SimpleOutput", "FilePath", + config_set_default_string(activeConfiguration, "SimpleOutput", + "FilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(basicConfig, "SimpleOutput", "RecFormat2", - DEFAULT_CONTAINER); - config_set_default_uint(basicConfig, "SimpleOutput", "VBitrate", 2500); - config_set_default_uint(basicConfig, "SimpleOutput", "ABitrate", 160); - config_set_default_bool(basicConfig, "SimpleOutput", "UseAdvanced", - false); - config_set_default_string(basicConfig, "SimpleOutput", "Preset", + config_set_default_string(activeConfiguration, "SimpleOutput", + "RecFormat2", DEFAULT_CONTAINER); + config_set_default_uint(activeConfiguration, "SimpleOutput", "VBitrate", + 2500); + config_set_default_uint(activeConfiguration, "SimpleOutput", "ABitrate", + 160); + config_set_default_bool(activeConfiguration, "SimpleOutput", + "UseAdvanced", false); + config_set_default_string(activeConfiguration, "SimpleOutput", "Preset", "veryfast"); - config_set_default_string(basicConfig, "SimpleOutput", "NVENCPreset2", - "p5"); - config_set_default_string(basicConfig, "SimpleOutput", "RecQuality", - "Stream"); - config_set_default_bool(basicConfig, "SimpleOutput", "RecRB", false); - config_set_default_int(basicConfig, "SimpleOutput", "RecRBTime", 20); - config_set_default_int(basicConfig, "SimpleOutput", "RecRBSize", 512); - config_set_default_string(basicConfig, "SimpleOutput", "RecRBPrefix", - "Replay"); - config_set_default_string(basicConfig, "SimpleOutput", + config_set_default_string(activeConfiguration, "SimpleOutput", + "NVENCPreset2", "p5"); + config_set_default_string(activeConfiguration, "SimpleOutput", + "RecQuality", "Stream"); + config_set_default_bool(activeConfiguration, "SimpleOutput", "RecRB", + false); + config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBTime", + 20); + config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBSize", + 512); + config_set_default_string(activeConfiguration, "SimpleOutput", + "RecRBPrefix", "Replay"); + config_set_default_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "aac"); - config_set_default_string(basicConfig, "SimpleOutput", + config_set_default_string(activeConfiguration, "SimpleOutput", "RecAudioEncoder", "aac"); - config_set_default_uint(basicConfig, "SimpleOutput", "RecTracks", - (1 << 0)); + config_set_default_uint(activeConfiguration, "SimpleOutput", + "RecTracks", (1 << 0)); - config_set_default_bool(basicConfig, "AdvOut", "ApplyServiceSettings", - true); - config_set_default_bool(basicConfig, "AdvOut", "UseRescale", false); - config_set_default_uint(basicConfig, "AdvOut", "TrackIndex", 1); - config_set_default_uint(basicConfig, "AdvOut", "VodTrackIndex", 2); - config_set_default_string(basicConfig, "AdvOut", "Encoder", "obs_x264"); + config_set_default_bool(activeConfiguration, "AdvOut", + "ApplyServiceSettings", true); + config_set_default_bool(activeConfiguration, "AdvOut", "UseRescale", + false); + config_set_default_uint(activeConfiguration, "AdvOut", "TrackIndex", 1); + config_set_default_uint(activeConfiguration, "AdvOut", "VodTrackIndex", + 2); + config_set_default_string(activeConfiguration, "AdvOut", "Encoder", + "obs_x264"); - config_set_default_string(basicConfig, "AdvOut", "RecType", "Standard"); + config_set_default_string(activeConfiguration, "AdvOut", "RecType", + "Standard"); - config_set_default_string(basicConfig, "AdvOut", "RecFilePath", + config_set_default_string(activeConfiguration, "AdvOut", "RecFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(basicConfig, "AdvOut", "RecFormat2", + config_set_default_string(activeConfiguration, "AdvOut", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_bool(basicConfig, "AdvOut", "RecUseRescale", false); - config_set_default_uint(basicConfig, "AdvOut", "RecTracks", (1 << 0)); - config_set_default_string(basicConfig, "AdvOut", "RecEncoder", "none"); - config_set_default_uint(basicConfig, "AdvOut", "FLVTrack", 1); - config_set_default_uint(basicConfig, "AdvOut", + config_set_default_bool(activeConfiguration, "AdvOut", "RecUseRescale", + false); + config_set_default_uint(activeConfiguration, "AdvOut", "RecTracks", + (1 << 0)); + config_set_default_string(activeConfiguration, "AdvOut", "RecEncoder", + "none"); + config_set_default_uint(activeConfiguration, "AdvOut", "FLVTrack", 1); + config_set_default_uint(activeConfiguration, "AdvOut", "StreamMultiTrackAudioMixes", 1); - config_set_default_bool(basicConfig, "AdvOut", "FFOutputToFile", true); - config_set_default_string(basicConfig, "AdvOut", "FFFilePath", + config_set_default_bool(activeConfiguration, "AdvOut", "FFOutputToFile", + true); + config_set_default_string(activeConfiguration, "AdvOut", "FFFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(basicConfig, "AdvOut", "FFExtension", "mp4"); - config_set_default_uint(basicConfig, "AdvOut", "FFVBitrate", 2500); - config_set_default_uint(basicConfig, "AdvOut", "FFVGOPSize", 250); - config_set_default_bool(basicConfig, "AdvOut", "FFUseRescale", false); - config_set_default_bool(basicConfig, "AdvOut", "FFIgnoreCompat", false); - config_set_default_uint(basicConfig, "AdvOut", "FFABitrate", 160); - config_set_default_uint(basicConfig, "AdvOut", "FFAudioMixes", 1); - - config_set_default_uint(basicConfig, "AdvOut", "Track1Bitrate", 160); - config_set_default_uint(basicConfig, "AdvOut", "Track2Bitrate", 160); - config_set_default_uint(basicConfig, "AdvOut", "Track3Bitrate", 160); - config_set_default_uint(basicConfig, "AdvOut", "Track4Bitrate", 160); - config_set_default_uint(basicConfig, "AdvOut", "Track5Bitrate", 160); - config_set_default_uint(basicConfig, "AdvOut", "Track6Bitrate", 160); - - config_set_default_uint(basicConfig, "AdvOut", "RecSplitFileTime", 15); - config_set_default_uint(basicConfig, "AdvOut", "RecSplitFileSize", - 2048); - - config_set_default_bool(basicConfig, "AdvOut", "RecRB", false); - config_set_default_uint(basicConfig, "AdvOut", "RecRBTime", 20); - config_set_default_int(basicConfig, "AdvOut", "RecRBSize", 512); - - config_set_default_uint(basicConfig, "Video", "BaseCX", cx); - config_set_default_uint(basicConfig, "Video", "BaseCY", cy); + config_set_default_string(activeConfiguration, "AdvOut", "FFExtension", + "mp4"); + config_set_default_uint(activeConfiguration, "AdvOut", "FFVBitrate", + 2500); + config_set_default_uint(activeConfiguration, "AdvOut", "FFVGOPSize", + 250); + config_set_default_bool(activeConfiguration, "AdvOut", "FFUseRescale", + false); + config_set_default_bool(activeConfiguration, "AdvOut", "FFIgnoreCompat", + false); + config_set_default_uint(activeConfiguration, "AdvOut", "FFABitrate", + 160); + config_set_default_uint(activeConfiguration, "AdvOut", "FFAudioMixes", + 1); + + config_set_default_uint(activeConfiguration, "AdvOut", "Track1Bitrate", + 160); + config_set_default_uint(activeConfiguration, "AdvOut", "Track2Bitrate", + 160); + config_set_default_uint(activeConfiguration, "AdvOut", "Track3Bitrate", + 160); + config_set_default_uint(activeConfiguration, "AdvOut", "Track4Bitrate", + 160); + config_set_default_uint(activeConfiguration, "AdvOut", "Track5Bitrate", + 160); + config_set_default_uint(activeConfiguration, "AdvOut", "Track6Bitrate", + 160); + + config_set_default_uint(activeConfiguration, "AdvOut", + "RecSplitFileTime", 15); + config_set_default_uint(activeConfiguration, "AdvOut", + "RecSplitFileSize", 2048); + + config_set_default_bool(activeConfiguration, "AdvOut", "RecRB", false); + config_set_default_uint(activeConfiguration, "AdvOut", "RecRBTime", 20); + config_set_default_int(activeConfiguration, "AdvOut", "RecRBSize", 512); + + config_set_default_uint(activeConfiguration, "Video", "BaseCX", cx); + config_set_default_uint(activeConfiguration, "Video", "BaseCY", cy); /* don't allow BaseCX/BaseCY to be susceptible to defaults changing */ - if (!config_has_user_value(basicConfig, "Video", "BaseCX") || - !config_has_user_value(basicConfig, "Video", "BaseCY")) { - config_set_uint(basicConfig, "Video", "BaseCX", cx); - config_set_uint(basicConfig, "Video", "BaseCY", cy); - config_save_safe(basicConfig, "tmp", nullptr); + if (!config_has_user_value(activeConfiguration, "Video", "BaseCX") || + !config_has_user_value(activeConfiguration, "Video", "BaseCY")) { + config_set_uint(activeConfiguration, "Video", "BaseCX", cx); + config_set_uint(activeConfiguration, "Video", "BaseCY", cy); + config_save_safe(activeConfiguration, "tmp", nullptr); } - config_set_default_string(basicConfig, "Output", "FilenameFormatting", + config_set_default_string(activeConfiguration, "Output", + "FilenameFormatting", "%CCYY-%MM-%DD %hh-%mm-%ss"); - config_set_default_bool(basicConfig, "Output", "DelayEnable", false); - config_set_default_uint(basicConfig, "Output", "DelaySec", 20); - config_set_default_bool(basicConfig, "Output", "DelayPreserve", true); + config_set_default_bool(activeConfiguration, "Output", "DelayEnable", + false); + config_set_default_uint(activeConfiguration, "Output", "DelaySec", 20); + config_set_default_bool(activeConfiguration, "Output", "DelayPreserve", + true); - config_set_default_bool(basicConfig, "Output", "Reconnect", true); - config_set_default_uint(basicConfig, "Output", "RetryDelay", 2); - config_set_default_uint(basicConfig, "Output", "MaxRetries", 25); + config_set_default_bool(activeConfiguration, "Output", "Reconnect", + true); + config_set_default_uint(activeConfiguration, "Output", "RetryDelay", 2); + config_set_default_uint(activeConfiguration, "Output", "MaxRetries", + 25); - config_set_default_string(basicConfig, "Output", "BindIP", "default"); - config_set_default_string(basicConfig, "Output", "IPFamily", + config_set_default_string(activeConfiguration, "Output", "BindIP", + "default"); + config_set_default_string(activeConfiguration, "Output", "IPFamily", "IPv4+IPv6"); - config_set_default_bool(basicConfig, "Output", "NewSocketLoopEnable", - false); - config_set_default_bool(basicConfig, "Output", "LowLatencyEnable", - false); + config_set_default_bool(activeConfiguration, "Output", + "NewSocketLoopEnable", false); + config_set_default_bool(activeConfiguration, "Output", + "LowLatencyEnable", false); int i = 0; uint32_t scale_cx = cx; @@ -1988,44 +2054,55 @@ bool OBSBasic::InitBasicConfigDefaults() scale_cy = uint32_t(double(cy) / scale); } - config_set_default_uint(basicConfig, "Video", "OutputCX", scale_cx); - config_set_default_uint(basicConfig, "Video", "OutputCY", scale_cy); + config_set_default_uint(activeConfiguration, "Video", "OutputCX", + scale_cx); + config_set_default_uint(activeConfiguration, "Video", "OutputCY", + scale_cy); /* don't allow OutputCX/OutputCY to be susceptible to defaults * changing */ - if (!config_has_user_value(basicConfig, "Video", "OutputCX") || - !config_has_user_value(basicConfig, "Video", "OutputCY")) { - config_set_uint(basicConfig, "Video", "OutputCX", scale_cx); - config_set_uint(basicConfig, "Video", "OutputCY", scale_cy); - config_save_safe(basicConfig, "tmp", nullptr); - } - - config_set_default_uint(basicConfig, "Video", "FPSType", 0); - config_set_default_string(basicConfig, "Video", "FPSCommon", "30"); - config_set_default_uint(basicConfig, "Video", "FPSInt", 30); - config_set_default_uint(basicConfig, "Video", "FPSNum", 30); - config_set_default_uint(basicConfig, "Video", "FPSDen", 1); - config_set_default_string(basicConfig, "Video", "ScaleType", "bicubic"); - config_set_default_string(basicConfig, "Video", "ColorFormat", "NV12"); - config_set_default_string(basicConfig, "Video", "ColorSpace", "709"); - config_set_default_string(basicConfig, "Video", "ColorRange", + if (!config_has_user_value(activeConfiguration, "Video", "OutputCX") || + !config_has_user_value(activeConfiguration, "Video", "OutputCY")) { + config_set_uint(activeConfiguration, "Video", "OutputCX", + scale_cx); + config_set_uint(activeConfiguration, "Video", "OutputCY", + scale_cy); + config_save_safe(activeConfiguration, "tmp", nullptr); + } + + config_set_default_uint(activeConfiguration, "Video", "FPSType", 0); + config_set_default_string(activeConfiguration, "Video", "FPSCommon", + "30"); + config_set_default_uint(activeConfiguration, "Video", "FPSInt", 30); + config_set_default_uint(activeConfiguration, "Video", "FPSNum", 30); + config_set_default_uint(activeConfiguration, "Video", "FPSDen", 1); + config_set_default_string(activeConfiguration, "Video", "ScaleType", + "bicubic"); + config_set_default_string(activeConfiguration, "Video", "ColorFormat", + "NV12"); + config_set_default_string(activeConfiguration, "Video", "ColorSpace", + "709"); + config_set_default_string(activeConfiguration, "Video", "ColorRange", "Partial"); - config_set_default_uint(basicConfig, "Video", "SdrWhiteLevel", 300); - config_set_default_uint(basicConfig, "Video", "HdrNominalPeakLevel", - 1000); + config_set_default_uint(activeConfiguration, "Video", "SdrWhiteLevel", + 300); + config_set_default_uint(activeConfiguration, "Video", + "HdrNominalPeakLevel", 1000); - config_set_default_string(basicConfig, "Audio", "MonitoringDeviceId", - "default"); + config_set_default_string(activeConfiguration, "Audio", + "MonitoringDeviceId", "default"); config_set_default_string( - basicConfig, "Audio", "MonitoringDeviceName", + activeConfiguration, "Audio", "MonitoringDeviceName", Str("Basic.Settings.Advanced.Audio.MonitoringDevice" ".Default")); - config_set_default_uint(basicConfig, "Audio", "SampleRate", 48000); - config_set_default_string(basicConfig, "Audio", "ChannelSetup", + config_set_default_uint(activeConfiguration, "Audio", "SampleRate", + 48000); + config_set_default_string(activeConfiguration, "Audio", "ChannelSetup", "Stereo"); - config_set_default_double(basicConfig, "Audio", "MeterDecayRate", - VOLUME_METER_DECAY_FAST); - config_set_default_uint(basicConfig, "Audio", "PeakMeterType", 0); + config_set_default_double(activeConfiguration, "Audio", + "MeterDecayRate", VOLUME_METER_DECAY_FAST); + config_set_default_uint(activeConfiguration, "Audio", "PeakMeterType", + 0); CheckExistingCookieId(); @@ -2036,16 +2113,16 @@ extern bool EncoderAvailable(const char *encoder); void OBSBasic::InitBasicConfigDefaults2() { - bool oldEncDefaults = config_get_bool(App()->GlobalConfig(), "General", + bool oldEncDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); bool useNV = EncoderAvailable("ffmpeg_nvenc") && !oldEncDefaults; - config_set_default_string(basicConfig, "SimpleOutput", "StreamEncoder", - useNV ? SIMPLE_ENCODER_NVENC - : SIMPLE_ENCODER_X264); - config_set_default_string(basicConfig, "SimpleOutput", "RecEncoder", - useNV ? SIMPLE_ENCODER_NVENC - : SIMPLE_ENCODER_X264); + config_set_default_string( + activeConfiguration, "SimpleOutput", "StreamEncoder", + useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); + config_set_default_string( + activeConfiguration, "SimpleOutput", "RecEncoder", + useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); const char *aac_default = "ffmpeg_aac"; if (EncoderAvailable("CoreAudio_AAC")) @@ -2053,47 +2130,37 @@ void OBSBasic::InitBasicConfigDefaults2() else if (EncoderAvailable("libfdk_aac")) aac_default = "libfdk_aac"; - config_set_default_string(basicConfig, "AdvOut", "AudioEncoder", - aac_default); - config_set_default_string(basicConfig, "AdvOut", "RecAudioEncoder", + config_set_default_string(activeConfiguration, "AdvOut", "AudioEncoder", aac_default); + config_set_default_string(activeConfiguration, "AdvOut", + "RecAudioEncoder", aac_default); } bool OBSBasic::InitBasicConfig() { ProfileScope("OBSBasic::InitBasicConfig"); - char configPath[512]; - - int ret = GetProfilePath(configPath, sizeof(configPath), ""); - if (ret <= 0) { - OBSErrorBox(nullptr, "Failed to get profile path"); - return false; - } - - if (os_mkdir(configPath) == MKDIR_ERROR) { - OBSErrorBox(nullptr, "Failed to create profile path"); - return false; - } + RefreshProfiles(true); - ret = GetProfilePath(configPath, sizeof(configPath), "basic.ini"); - if (ret <= 0) { - OBSErrorBox(nullptr, "Failed to get basic.ini path"); - return false; - } + std::string currentProfileName{ + config_get_string(App()->GetUserConfig(), "Basic", "Profile")}; - int code = basicConfig.Open(configPath, CONFIG_OPEN_ALWAYS); - if (code != CONFIG_SUCCESS) { - OBSErrorBox(NULL, "Failed to open basic.ini: %d", code); - return false; - } + auto foundProfile = GetProfileByName(currentProfileName); - if (config_get_string(basicConfig, "General", "Name") == nullptr) { - const char *curName = config_get_string(App()->GlobalConfig(), - "Basic", "Profile"); + if (!foundProfile) { + const OBSProfile &newProfile = + CreateProfile(currentProfileName); - config_set_string(basicConfig, "General", "Name", curName); - basicConfig.SaveSafe("tmp"); + ActivateProfile(newProfile); + } else { + // TODO: Remove duplicate code from OBS initialization and just use ActivateProfile here instead + int code = activeConfiguration.Open( + foundProfile.value().profileFile.u8string().c_str(), + CONFIG_OPEN_ALWAYS); + if (code != CONFIG_SUCCESS) { + OBSErrorBox(NULL, "Failed to open basic.ini: %d", code); + return false; + } } return InitBasicConfigDefaults(); @@ -2196,7 +2263,8 @@ void OBSBasic::ResetOutputs() { ProfileScope("OBSBasic::ResetOutputs"); - const char *mode = config_get_string(basicConfig, "Output", "Mode"); + const char *mode = + config_get_string(activeConfiguration, "Output", "Mode"); bool advOut = astrcmpi(mode, "Advanced") == 0; if ((!outputHandler || !outputHandler->Active()) && @@ -2264,29 +2332,13 @@ void OBSBasic::OBSInit() { ProfileScope("OBSBasic::OBSInit"); - const char *sceneCollection = config_get_string( - App()->GlobalConfig(), "Basic", "SceneCollectionFile"); - char savePath[1024]; - char fileName[1024]; - int ret; - - if (!sceneCollection) - throw "Failed to get scene collection name"; - - ret = snprintf(fileName, sizeof(fileName), - "obs-studio/basic/scenes/%s.json", sceneCollection); - if (ret <= 0) - throw "Failed to create scene collection file name"; - - ret = GetConfigPath(savePath, sizeof(savePath), fileName); - if (ret <= 0) - throw "Failed to get scene collection json file path"; - if (!InitBasicConfig()) throw "Failed to load basic.ini"; if (!ResetAudio()) throw "Failed to initialize audio"; + int ret = 0; + ret = ResetVideo(); switch (ret) { @@ -2304,9 +2356,9 @@ void OBSBasic::OBSInit() /* load audio monitoring */ if (obs_audio_monitoring_available()) { const char *device_name = config_get_string( - basicConfig, "Audio", "MonitoringDeviceName"); - const char *device_id = config_get_string(basicConfig, "Audio", - "MonitoringDeviceId"); + activeConfiguration, "Audio", "MonitoringDeviceName"); + const char *device_id = config_get_string( + activeConfiguration, "Audio", "MonitoringDeviceId"); obs_set_audio_monitoring_device(device_name, device_id); @@ -2333,6 +2385,12 @@ void OBSBasic::OBSInit() AddExtraModulePaths(); } + /* Modules can access frontend information (i.e. profile and scene collection data) during their initialization, and some modules (e.g. obs-websockets) are known to use the filesystem location of the current profile in their own code. + + Thus the profile and scene collection discovery needs to happen before any access to that information (but after intializing global settings) to ensure legacy code gets valid path information. + */ + RefreshSceneCollections(true); + blog(LOG_INFO, "---------------------------------"); obs_load_all_modules2(&mfi); blog(LOG_INFO, "---------------------------------"); @@ -2370,14 +2428,14 @@ void OBSBasic::OBSInit() InitPrimitives(); sceneDuplicationMode = config_get_bool( - App()->GlobalConfig(), "BasicWindow", "SceneDuplicationMode"); - swapScenesMode = config_get_bool(App()->GlobalConfig(), "BasicWindow", + App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode"); + swapScenesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode"); editPropertiesMode = config_get_bool( - App()->GlobalConfig(), "BasicWindow", "EditPropertiesMode"); + App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode"); if (!opt_studio_mode) { - SetPreviewProgramMode(config_get_bool(App()->GlobalConfig(), + SetPreviewProgramMode(config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode")); } else { @@ -2385,14 +2443,14 @@ void OBSBasic::OBSInit() opt_studio_mode = false; } -#define SET_VISIBILITY(name, control) \ - do { \ - if (config_has_user_value(App()->GlobalConfig(), \ - "BasicWindow", name)) { \ - bool visible = config_get_bool(App()->GlobalConfig(), \ - "BasicWindow", name); \ - ui->control->setChecked(visible); \ - } \ +#define SET_VISIBILITY(name, control) \ + do { \ + if (config_has_user_value(App()->GetUserConfig(), \ + "BasicWindow", name)) { \ + bool visible = config_get_bool(App()->GetUserConfig(), \ + "BasicWindow", name); \ + ui->control->setChecked(visible); \ + } \ } while (false) SET_VISIBILITY("ShowListboxToolbars", toggleListboxToolbars); @@ -2400,11 +2458,11 @@ void OBSBasic::OBSInit() #undef SET_VISIBILITY bool sourceIconsVisible = config_get_bool( - GetGlobalConfig(), "BasicWindow", "ShowSourceIcons"); + App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); ui->toggleSourceIcons->setChecked(sourceIconsVisible); bool contextVisible = config_get_bool( - App()->GlobalConfig(), "BasicWindow", "ShowContextToolbars"); + App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars"); ui->toggleContextBar->setChecked(contextVisible); ui->contextContainer->setVisible(contextVisible); if (contextVisible) @@ -2414,13 +2472,25 @@ void OBSBasic::OBSInit() { ProfileScope("OBSBasic::Load"); disableSaving--; - Load(savePath); + + try { + const OBSSceneCollection ¤tCollection = + GetCurrentSceneCollection(); + ActivateSceneCollection(currentCollection); + } catch (const std::invalid_argument &) { + const std::string collectionName = + config_get_string(App()->GetUserConfig(), + "Basic", "SceneCollection"); + + SetupNewSceneCollection(collectionName); + } + disableSaving++; } loaded = true; - previewEnabled = config_get_bool(App()->GlobalConfig(), "BasicWindow", + previewEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled"); if (!previewEnabled && !IsPreviewProgramMode()) @@ -2432,8 +2502,6 @@ void OBSBasic::OBSInit() Qt::QueuedConnection, Q_ARG(bool, true)); - RefreshSceneCollections(); - RefreshProfiles(); disableSaving--; auto addDisplay = [this](OBSQTDisplay *window) { @@ -2449,10 +2517,10 @@ void OBSBasic::OBSInit() /* Show the main window, unless the tray icon isn't available * or neither the setting nor flag for starting minimized is set. */ - bool sysTrayEnabled = config_get_bool(App()->GlobalConfig(), + bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); bool sysTrayWhenStarted = config_get_bool( - App()->GlobalConfig(), "BasicWindow", "SysTrayWhenStarted"); + App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); bool hideWindowOnStart = QSystemTrayIcon::isSystemTrayAvailable() && sysTrayEnabled && (opt_minimize_tray || sysTrayWhenStarted); @@ -2464,8 +2532,8 @@ void OBSBasic::OBSInit() show(); #endif - bool alwaysOnTop = config_get_bool(App()->GlobalConfig(), "BasicWindow", - "AlwaysOnTop"); + bool alwaysOnTop = config_get_bool(App()->GetUserConfig(), + "BasicWindow", "AlwaysOnTop"); #ifdef ENABLE_WAYLAND bool isWayland = obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND; @@ -2522,7 +2590,7 @@ void OBSBasic::OBSInit() #endif const char *dockStateStr = config_get_string( - App()->GlobalConfig(), "BasicWindow", "DockState"); + App()->GetUserConfig(), "BasicWindow", "DockState"); if (!dockStateStr) { on_resetDocks_triggered(true); @@ -2533,28 +2601,29 @@ void OBSBasic::OBSInit() on_resetDocks_triggered(true); } - bool pre23Defaults = config_get_bool(App()->GlobalConfig(), "General", + bool pre23Defaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); if (pre23Defaults) { bool resetDockLock23 = config_get_bool( - App()->GlobalConfig(), "General", "ResetDockLock23"); + App()->GetUserConfig(), "General", "ResetDockLock23"); if (!resetDockLock23) { - config_set_bool(App()->GlobalConfig(), "General", + config_set_bool(App()->GetUserConfig(), "General", "ResetDockLock23", true); - config_remove_value(App()->GlobalConfig(), + config_remove_value(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - config_save_safe(App()->GlobalConfig(), "tmp", nullptr); + config_save_safe(App()->GetUserConfig(), "tmp", + nullptr); } } - bool docksLocked = config_get_bool(App()->GlobalConfig(), "BasicWindow", - "DocksLocked"); + bool docksLocked = config_get_bool(App()->GetUserConfig(), + "BasicWindow", "DocksLocked"); on_lockDocks_toggled(docksLocked); ui->lockDocks->blockSignals(true); ui->lockDocks->setChecked(docksLocked); ui->lockDocks->blockSignals(false); - bool sideDocks = config_get_bool(App()->GlobalConfig(), "BasicWindow", + bool sideDocks = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks"); on_sideDocks_toggled(sideDocks); ui->sideDocks->blockSignals(true); @@ -2569,15 +2638,15 @@ void OBSBasic::OBSInit() disableColorSpaceConversion(this); #endif - bool has_last_version = config_has_user_value(App()->GlobalConfig(), + bool has_last_version = config_has_user_value(App()->GetUserConfig(), "General", "LastVersion"); bool first_run = - config_get_bool(App()->GlobalConfig(), "General", "FirstRun"); + config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); if (!first_run) { - config_set_bool(App()->GlobalConfig(), "General", "FirstRun", + config_set_bool(App()->GetUserConfig(), "General", "FirstRun", true); - config_save_safe(App()->GlobalConfig(), "tmp", nullptr); + config_save_safe(App()->GetUserConfig(), "tmp", nullptr); } if (!first_run && !has_last_version && !Active()) @@ -2587,21 +2656,22 @@ void OBSBasic::OBSInit() #if (defined(_WIN32) || defined(__APPLE__)) && \ (OBS_RELEASE_CANDIDATE > 0 || OBS_BETA > 0) /* Automatically set branch to "beta" the first time a pre-release build is run. */ - if (!config_get_bool(App()->GlobalConfig(), "General", + if (!config_get_bool(App()->GetUserConfig(), "General", "AutoBetaOptIn")) { - config_set_string(App()->GlobalConfig(), "General", + config_set_string(App()->GetUserConfig(), "General", "UpdateBranch", "beta"); - config_set_bool(App()->GlobalConfig(), "General", + config_set_bool(App()->GetUserConfig(), "General", "AutoBetaOptIn", true); - config_save_safe(App()->GlobalConfig(), "tmp", nullptr); + config_save_safe(App()->GetUserConfig(), "tmp", nullptr); } #endif TimedCheckForUpdates(); - ToggleMixerLayout(config_get_bool(App()->GlobalConfig(), "BasicWindow", + ToggleMixerLayout(config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - if (config_get_bool(basicConfig, "General", "OpenStatsOnStartup")) + if (config_get_bool(activeConfiguration, "General", + "OpenStatsOnStartup")) on_stats_triggered(); OBSBasicStats::InitializeValues(); @@ -2707,7 +2777,7 @@ void OBSBasic::OnFirstLoad() Auth::Load(); bool showLogViewerOnStartup = config_get_bool( - App()->GlobalConfig(), "LogViewer", "ShowLogStartup"); + App()->GetUserConfig(), "LogViewer", "ShowLogStartup"); if (showLogViewerOnStartup) on_actionViewCurrentLog_triggered(); @@ -2775,28 +2845,28 @@ void OBSBasic::ReceivedIntroJson(const QString &text) constexpr uint64_t currentVersion = (uint64_t)LIBOBS_API_VER << 16ULL | OBS_RELEASE_CANDIDATE << 8ULL | OBS_BETA; - uint64_t lastVersion = config_get_uint(App()->GlobalConfig(), "General", + uint64_t lastVersion = config_get_uint(App()->GetAppConfig(), "General", lastInfoVersion); int current_version_increment = -1; if ((lastVersion & ~0xFFFF0000ULL) < (currentVersion & ~0xFFFF0000ULL)) { - config_set_int(App()->GlobalConfig(), "General", + config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - config_set_uint(App()->GlobalConfig(), "General", + config_set_uint(App()->GetAppConfig(), "General", lastInfoVersion, currentVersion); } else { current_version_increment = config_get_int( - App()->GlobalConfig(), "General", "InfoIncrement"); + App()->GetAppConfig(), "General", "InfoIncrement"); } if (info_increment <= current_version_increment) { return; } - config_set_int(App()->GlobalConfig(), "General", "InfoIncrement", + config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", info_increment); - config_save_safe(App()->GlobalConfig(), "tmp", nullptr); + config_save_safe(App()->GetAppConfig(), "tmp", nullptr); cef->init_browser(); @@ -2940,7 +3010,7 @@ void OBSBasic::CreateHotkeys() auto LoadHotkeyData = [&](const char *name) -> OBSData { const char *info = - config_get_string(basicConfig, "Hotkeys", name); + config_get_string(activeConfiguration, "Hotkeys", name); if (!info) return {}; @@ -2962,16 +3032,16 @@ void OBSBasic::CreateHotkeys() const char *name1, const char *oldName = NULL) { if (oldName) { - const auto info = config_get_string(basicConfig, + const auto info = config_get_string(activeConfiguration, "Hotkeys", oldName); if (info) { - config_set_string(basicConfig, "Hotkeys", name0, - info); - config_set_string(basicConfig, "Hotkeys", name1, - info); - config_remove_value(basicConfig, "Hotkeys", - oldName); - config_save(basicConfig); + config_set_string(activeConfiguration, + "Hotkeys", name0, info); + config_set_string(activeConfiguration, + "Hotkeys", name1, info); + config_remove_value(activeConfiguration, + "Hotkeys", oldName); + activeConfiguration.Save(); } } OBSDataArrayAutoRelease array0 = @@ -3284,26 +3354,27 @@ OBSBasic::~OBSBasic() * expect or want it to. */ QApplication::sendPostedEvents(nullptr); - config_set_int(App()->GlobalConfig(), "General", "LastVersion", + config_set_int(App()->GetAppConfig(), "General", "LastVersion", LIBOBS_API_VER); + config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - config_set_bool(App()->GlobalConfig(), "BasicWindow", "PreviewEnabled", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled", previewEnabled); - config_set_bool(App()->GlobalConfig(), "BasicWindow", "AlwaysOnTop", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop", ui->actionAlwaysOnTop->isChecked()); - config_set_bool(App()->GlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode", sceneDuplicationMode); - config_set_bool(App()->GlobalConfig(), "BasicWindow", "SwapScenesMode", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode", swapScenesMode); - config_set_bool(App()->GlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode", editPropertiesMode); - config_set_bool(App()->GlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode", IsPreviewProgramMode()); - config_set_bool(App()->GlobalConfig(), "BasicWindow", "DocksLocked", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked", ui->lockDocks->isChecked()); - config_set_bool(App()->GlobalConfig(), "BasicWindow", "SideDocks", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks", ui->sideDocks->isChecked()); - config_save_safe(App()->GlobalConfig(), "tmp", nullptr); + config_save_safe(App()->GetUserConfig(), "tmp", nullptr); #ifdef BROWSER_AVAILABLE DestroyPanelCookieManager(); @@ -3341,26 +3412,14 @@ void OBSBasic::SaveProjectDeferred() projectChanged = false; - const char *sceneCollection = config_get_string( - App()->GlobalConfig(), "Basic", "SceneCollectionFile"); - - char savePath[1024]; - char fileName[1024]; - int ret; - - if (!sceneCollection) - return; - - ret = snprintf(fileName, sizeof(fileName), - "obs-studio/basic/scenes/%s.json", sceneCollection); - if (ret <= 0) - return; - - ret = GetConfigPath(savePath, sizeof(savePath), fileName); - if (ret <= 0) - return; + try { + const OBSSceneCollection ¤tCollection = + GetCurrentSceneCollection(); - Save(savePath); + Save(currentCollection.collectionFile.u8string().c_str()); + } catch (const std::invalid_argument &error) { + blog(LOG_ERROR, "%s", error.what()); + } } OBSSource OBSBasic::GetProgramSource() @@ -4027,7 +4086,7 @@ void OBSBasic::VolControlContextMenu() QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); toggleControlLayoutAction.setCheckable(true); toggleControlLayoutAction.setChecked(config_get_bool( - GetGlobalConfig(), "BasicWindow", "VerticalVolControl")); + App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); /* ------------------- */ @@ -4126,7 +4185,7 @@ void OBSBasic::StackedMixerAreaContextMenuRequested() QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); toggleControlLayoutAction.setCheckable(true); toggleControlLayoutAction.setChecked(config_get_bool( - GetGlobalConfig(), "BasicWindow", "VerticalVolControl")); + App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); /* ------------------- */ @@ -4166,10 +4225,10 @@ void OBSBasic::ToggleMixerLayout(bool vertical) void OBSBasic::ToggleVolControlLayout() { - bool vertical = !config_get_bool(GetGlobalConfig(), "BasicWindow", + bool vertical = !config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - config_set_bool(GetGlobalConfig(), "BasicWindow", "VerticalVolControl", - vertical); + config_set_bool(App()->GetUserConfig(), "BasicWindow", + "VerticalVolControl", vertical); ToggleMixerLayout(vertical); // We need to store it so we can delete current and then add @@ -4193,18 +4252,18 @@ void OBSBasic::ActivateAudioSource(OBSSource source) if (!obs_source_audio_active(source)) return; - bool vertical = config_get_bool(GetGlobalConfig(), "BasicWindow", + bool vertical = config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); VolControl *vol = new VolControl(source, true, vertical); vol->EnableSlider(!SourceVolumeLocked(source)); - double meterDecayRate = - config_get_double(basicConfig, "Audio", "MeterDecayRate"); + double meterDecayRate = config_get_double(activeConfiguration, "Audio", + "MeterDecayRate"); vol->SetMeterDecayRate(meterDecayRate); uint32_t peakMeterTypeIdx = - config_get_uint(basicConfig, "Audio", "PeakMeterType"); + config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); enum obs_peak_meter_type peakMeterType; switch (peakMeterTypeIdx) { @@ -4286,21 +4345,21 @@ void OBSBasic::TimedCheckForUpdates() { if (App()->IsUpdaterDisabled()) return; - if (!config_get_bool(App()->GlobalConfig(), "General", + if (!config_get_bool(App()->GetUserConfig(), "General", "EnableAutoUpdates")) return; #if defined(ENABLE_SPARKLE_UPDATER) CheckForUpdates(false); #elif _WIN32 - long long lastUpdate = config_get_int(App()->GlobalConfig(), "General", + long long lastUpdate = config_get_int(App()->GetAppConfig(), "General", "LastUpdateCheck"); uint32_t lastVersion = - config_get_int(App()->GlobalConfig(), "General", "LastVersion"); + config_get_int(App()->GetAppConfig(), "General", "LastVersion"); if (lastVersion < LIBOBS_API_VER) { lastUpdate = 0; - config_set_int(App()->GlobalConfig(), "General", + config_set_int(App()->GetAppConfig(), "General", "LastUpdateCheck", 0); } @@ -4916,10 +4975,10 @@ static inline int AttemptToResetVideo(struct obs_video_info *ovi) return obs_reset_video(ovi); } -static inline enum obs_scale_type GetScaleType(ConfigFile &basicConfig) +static inline enum obs_scale_type GetScaleType(ConfigFile &activeConfiguration) { const char *scaleTypeStr = - config_get_string(basicConfig, "Video", "ScaleType"); + config_get_string(activeConfiguration, "Video", "ScaleType"); if (astrcmpi(scaleTypeStr, "bilinear") == 0) return OBS_SCALE_BILINEAR; @@ -4977,7 +5036,7 @@ static inline enum video_colorspace GetVideoColorSpaceFromName(const char *name) void OBSBasic::ResetUI() { bool studioPortraitLayout = config_get_bool( - GetGlobalConfig(), "BasicWindow", "StudioPortraitLayout"); + App()->GetUserConfig(), "BasicWindow", "StudioPortraitLayout"); if (studioPortraitLayout) ui->previewLayout->setDirection(QBoxLayout::BottomToTop); @@ -5000,43 +5059,43 @@ int OBSBasic::ResetVideo() GetConfigFPS(ovi.fps_num, ovi.fps_den); const char *colorFormat = - config_get_string(basicConfig, "Video", "ColorFormat"); + config_get_string(activeConfiguration, "Video", "ColorFormat"); const char *colorSpace = - config_get_string(basicConfig, "Video", "ColorSpace"); + config_get_string(activeConfiguration, "Video", "ColorSpace"); const char *colorRange = - config_get_string(basicConfig, "Video", "ColorRange"); + config_get_string(activeConfiguration, "Video", "ColorRange"); ovi.graphics_module = App()->GetRenderModule(); - ovi.base_width = - (uint32_t)config_get_uint(basicConfig, "Video", "BaseCX"); - ovi.base_height = - (uint32_t)config_get_uint(basicConfig, "Video", "BaseCY"); - ovi.output_width = - (uint32_t)config_get_uint(basicConfig, "Video", "OutputCX"); - ovi.output_height = - (uint32_t)config_get_uint(basicConfig, "Video", "OutputCY"); + ovi.base_width = (uint32_t)config_get_uint(activeConfiguration, "Video", + "BaseCX"); + ovi.base_height = (uint32_t)config_get_uint(activeConfiguration, + "Video", "BaseCY"); + ovi.output_width = (uint32_t)config_get_uint(activeConfiguration, + "Video", "OutputCX"); + ovi.output_height = (uint32_t)config_get_uint(activeConfiguration, + "Video", "OutputCY"); ovi.output_format = GetVideoFormatFromName(colorFormat); ovi.colorspace = GetVideoColorSpaceFromName(colorSpace); ovi.range = astrcmpi(colorRange, "Full") == 0 ? VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL; ovi.adapter = - config_get_uint(App()->GlobalConfig(), "Video", "AdapterIdx"); + config_get_uint(App()->GetUserConfig(), "Video", "AdapterIdx"); ovi.gpu_conversion = true; - ovi.scale_type = GetScaleType(basicConfig); + ovi.scale_type = GetScaleType(activeConfiguration); if (ovi.base_width < 32 || ovi.base_height < 32) { ovi.base_width = 1920; ovi.base_height = 1080; - config_set_uint(basicConfig, "Video", "BaseCX", 1920); - config_set_uint(basicConfig, "Video", "BaseCY", 1080); + config_set_uint(activeConfiguration, "Video", "BaseCX", 1920); + config_set_uint(activeConfiguration, "Video", "BaseCY", 1080); } if (ovi.output_width < 32 || ovi.output_height < 32) { ovi.output_width = ovi.base_width; ovi.output_height = ovi.base_height; - config_set_uint(basicConfig, "Video", "OutputCX", + config_set_uint(activeConfiguration, "Video", "OutputCX", ovi.base_width); - config_set_uint(basicConfig, "Video", "OutputCY", + config_set_uint(activeConfiguration, "Video", "OutputCY", ovi.base_height); } @@ -5052,9 +5111,9 @@ int OBSBasic::ResetVideo() ResizeProgram(ovi.base_width, ovi.base_height); const float sdr_white_level = (float)config_get_uint( - basicConfig, "Video", "SdrWhiteLevel"); + activeConfiguration, "Video", "SdrWhiteLevel"); const float hdr_nominal_peak_level = (float)config_get_uint( - basicConfig, "Video", "HdrNominalPeakLevel"); + activeConfiguration, "Video", "HdrNominalPeakLevel"); obs_set_video_levels(sdr_white_level, hdr_nominal_peak_level); OBSBasicStats::InitializeValues(); OBSProjector::UpdateMultiviewProjectors(); @@ -5079,10 +5138,10 @@ bool OBSBasic::ResetAudio() struct obs_audio_info2 ai = {}; ai.samples_per_sec = - config_get_uint(basicConfig, "Audio", "SampleRate"); + config_get_uint(activeConfiguration, "Audio", "SampleRate"); const char *channelSetupStr = - config_get_string(basicConfig, "Audio", "ChannelSetup"); + config_get_string(activeConfiguration, "Audio", "ChannelSetup"); if (strcmp(channelSetupStr, "Mono") == 0) ai.speakers = SPEAKERS_MONO; @@ -5100,7 +5159,7 @@ bool OBSBasic::ResetAudio() ai.speakers = SPEAKERS_STEREO; bool lowLatencyAudioBuffering = config_get_bool( - GetGlobalConfig(), "Audio", "LowLatencyAudioBuffering"); + App()->GetUserConfig(), "Audio", "LowLatencyAudioBuffering"); if (lowLatencyAudioBuffering) { ai.max_buffering_ms = 20; ai.fixed_buffering = true; @@ -5365,12 +5424,12 @@ void OBSBasic::closeEvent(QCloseEvent *event) #endif if (isVisible()) - config_set_string(App()->GlobalConfig(), "BasicWindow", + config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", saveGeometry().toBase64().constData()); - bool confirmOnExit = - config_get_bool(GetGlobalConfig(), "General", "ConfirmOnExit"); + bool confirmOnExit = config_get_bool(App()->GetUserConfig(), "General", + "ConfirmOnExit"); if (confirmOnExit && outputHandler && outputHandler->Active() && !clearingFailed) { @@ -5437,7 +5496,7 @@ void OBSBasic::closeEvent(QCloseEvent *event) delete extraBrowsers; - config_set_string(App()->GlobalConfig(), "BasicWindow", "DockState", + config_set_string(App()->GetUserConfig(), "BasicWindow", "DockState", saveState().toBase64().constData()); #ifdef BROWSER_AVAILABLE @@ -5513,15 +5572,18 @@ void OBSBasic::changeEvent(QEvent *event) void OBSBasic::on_actionShow_Recordings_triggered() { - const char *mode = config_get_string(basicConfig, "Output", "Mode"); - const char *type = config_get_string(basicConfig, "AdvOut", "RecType"); + const char *mode = + config_get_string(activeConfiguration, "Output", "Mode"); + const char *type = + config_get_string(activeConfiguration, "AdvOut", "RecType"); const char *adv_path = strcmp(type, "Standard") - ? config_get_string(basicConfig, "AdvOut", "FFFilePath") - : config_get_string(basicConfig, "AdvOut", + ? config_get_string(activeConfiguration, "AdvOut", + "FFFilePath") + : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); const char *path = strcmp(mode, "Advanced") - ? config_get_string(basicConfig, + ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") : adv_path; @@ -5536,13 +5598,14 @@ void OBSBasic::on_actionRemux_triggered() return; } - const char *mode = config_get_string(basicConfig, "Output", "Mode"); + const char *mode = + config_get_string(activeConfiguration, "Output", "Mode"); const char *path = strcmp(mode, "Advanced") - ? config_get_string(basicConfig, + ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : config_get_string(basicConfig, "AdvOut", - "RecFilePath"); + : config_get_string(activeConfiguration, + "AdvOut", "RecFilePath"); OBSRemux *remuxDlg; remuxDlg = new OBSRemux(path, this); @@ -5642,7 +5705,7 @@ void OBSBasic::on_actionAdvAudioProperties_triggered() return; } - bool iconsVisible = config_get_bool(App()->GlobalConfig(), + bool iconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); advAudioWindow = new OBSBasicAdvAudio(this); @@ -5665,7 +5728,7 @@ void OBSBasic::on_actionMixerToolbarMenu_triggered() QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); toggleControlLayoutAction.setCheckable(true); toggleControlLayoutAction.setChecked(config_get_bool( - GetGlobalConfig(), "BasicWindow", "VerticalVolControl")); + App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, Qt::DirectConnection); @@ -5861,14 +5924,15 @@ void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) void OBSBasic::on_actionSceneListMode_triggered() { ui->scenes->SetGridMode(false); - config_set_bool(App()->GlobalConfig(), "BasicWindow", "gridMode", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); } void OBSBasic::on_actionSceneGridMode_triggered() { ui->scenes->SetGridMode(true); - config_set_bool(App()->GlobalConfig(), "BasicWindow", "gridMode", true); + config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", + true); } void OBSBasic::GridActionClicked() @@ -5881,7 +5945,7 @@ void OBSBasic::GridActionClicked() else ui->actionSceneListMode->setChecked(true); - config_set_bool(App()->GlobalConfig(), "BasicWindow", "gridMode", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", gridMode); } @@ -6405,7 +6469,7 @@ void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem) if (IsPreviewProgramMode()) { bool doubleClickSwitch = - config_get_bool(App()->GlobalConfig(), "BasicWindow", + config_get_bool(App()->GetUserConfig(), "BasicWindow", "TransitionOnDoubleClick"); if (doubleClickSwitch) @@ -6795,7 +6859,7 @@ void OBSBasic::on_actionMoveToBottom_triggered() static BPtr ReadLogFile(const char *subdir, const char *log) { char logDir[512]; - if (GetConfigPath(logDir, sizeof(logDir), subdir) <= 0) + if (GetAppConfigPath(logDir, sizeof(logDir), subdir) <= 0) return nullptr; string path = logDir; @@ -6850,7 +6914,7 @@ void OBSBasic::UploadLog(const char *subdir, const char *file, const bool crash) void OBSBasic::on_actionShowLogs_triggered() { char logDir[512]; - if (GetConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0) + if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0) return; QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); @@ -6883,7 +6947,7 @@ void OBSBasic::on_actionViewCurrentLog_triggered() void OBSBasic::on_actionShowCrashLogs_triggered() { char logDir[512]; - if (GetConfigPath(logDir, sizeof(logDir), "obs-studio/crashes") <= 0) + if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/crashes") <= 0) return; QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); @@ -7208,13 +7272,14 @@ void OBSBasic::ShowYouTubeAutoStartWarning() msgbox.exec(); if (cb->isChecked()) { - config_set_bool(App()->GlobalConfig(), "General", + config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart", true); - config_save_safe(App()->GlobalConfig(), "tmp", nullptr); + config_save_safe(App()->GetUserConfig(), "tmp", + nullptr); } }; - bool warned = config_get_bool(App()->GlobalConfig(), "General", + bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart"); if (!warned) { QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, @@ -7285,13 +7350,13 @@ void OBSBasic::StartStreaming() } bool recordWhenStreaming = - config_get_bool(GetGlobalConfig(), "BasicWindow", + config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); if (recordWhenStreaming) StartRecording(); bool replayBufferWhileStreaming = - config_get_bool(GetGlobalConfig(), "BasicWindow", + config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); if (replayBufferWhileStreaming) StartReplayBuffer(); @@ -7344,7 +7409,8 @@ void OBSBasic::BroadcastButtonClicked() emit BroadcastStreamStarted(autoStopBroadcast); } else if (!autoStopBroadcast) { #ifdef YOUTUBE_ENABLED - bool confirm = config_get_bool(GetGlobalConfig(), "BasicWindow", + bool confirm = config_get_bool(App()->GetUserConfig(), + "BasicWindow", "WarnBeforeStoppingStream"); if (confirm && isVisible()) { QMessageBox::StandardButton button = OBSMessageBox::question( @@ -7409,7 +7475,7 @@ void OBSBasic::SetupBroadcast() #ifdef _WIN32 static inline void UpdateProcessPriority() { - const char *priority = config_get_string(App()->GlobalConfig(), + const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); if (priority && strcmp(priority, "Normal") != 0) SetProcessPriority(priority); @@ -7417,7 +7483,7 @@ static inline void UpdateProcessPriority() static inline void ClearProcessPriority() { - const char *priority = config_get_string(App()->GlobalConfig(), + const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); if (priority && strcmp(priority, "Normal") != 0) SetProcessPriority("Normal"); @@ -7539,17 +7605,18 @@ void OBSBasic::StopStreaming() OnDeactivate(); bool recordWhenStreaming = config_get_bool( - GetGlobalConfig(), "BasicWindow", "RecordWhenStreaming"); + App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); bool keepRecordingWhenStreamStops = - config_get_bool(GetGlobalConfig(), "BasicWindow", + config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); if (recordWhenStreaming && !keepRecordingWhenStreamStops) StopRecording(); - bool replayBufferWhileStreaming = config_get_bool( - GetGlobalConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); + bool replayBufferWhileStreaming = + config_get_bool(App()->GetUserConfig(), "BasicWindow", + "ReplayBufferWhileStreaming"); bool keepReplayBufferStreamStops = - config_get_bool(GetGlobalConfig(), "BasicWindow", + config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) StopReplayBuffer(); @@ -7581,17 +7648,18 @@ void OBSBasic::ForceStopStreaming() OnDeactivate(); bool recordWhenStreaming = config_get_bool( - GetGlobalConfig(), "BasicWindow", "RecordWhenStreaming"); + App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); bool keepRecordingWhenStreamStops = - config_get_bool(GetGlobalConfig(), "BasicWindow", + config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); if (recordWhenStreaming && !keepRecordingWhenStreamStops) StopRecording(); - bool replayBufferWhileStreaming = config_get_bool( - GetGlobalConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); + bool replayBufferWhileStreaming = + config_get_bool(App()->GetUserConfig(), "BasicWindow", + "ReplayBufferWhileStreaming"); bool keepReplayBufferStreamStops = - config_get_bool(GetGlobalConfig(), "BasicWindow", + config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) StopReplayBuffer(); @@ -7997,13 +8065,14 @@ void OBSBasic::ShowReplayBufferPauseWarning() msgbox.exec(); if (cb->isChecked()) { - config_set_bool(App()->GlobalConfig(), "General", + config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing", true); - config_save_safe(App()->GlobalConfig(), "tmp", nullptr); + config_save_safe(App()->GetUserConfig(), "tmp", + nullptr); } }; - bool warned = config_get_bool(App()->GlobalConfig(), "General", + bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing"); if (!warned) { QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, @@ -8242,7 +8311,8 @@ void OBSBasic::OnVirtualCamStop(int) void OBSBasic::StreamActionTriggered() { if (outputHandler->StreamingActive()) { - bool confirm = config_get_bool(GetGlobalConfig(), "BasicWindow", + bool confirm = config_get_bool(App()->GetUserConfig(), + "BasicWindow", "WarnBeforeStoppingStream"); #ifdef YOUTUBE_ENABLED @@ -8294,7 +8364,8 @@ void OBSBasic::StreamActionTriggered() return; } - bool confirm = config_get_bool(GetGlobalConfig(), "BasicWindow", + bool confirm = config_get_bool(App()->GetUserConfig(), + "BasicWindow", "WarnBeforeStartingStream"); bool bwtest = false; @@ -8336,7 +8407,8 @@ void OBSBasic::StreamActionTriggered() void OBSBasic::RecordActionTriggered() { if (outputHandler->RecordingActive()) { - bool confirm = config_get_bool(GetGlobalConfig(), "BasicWindow", + bool confirm = config_get_bool(App()->GetUserConfig(), + "BasicWindow", "WarnBeforeStoppingRecord"); if (confirm && isVisible()) { @@ -8461,7 +8533,7 @@ void OBSBasic::on_actionShowWhatsNew_triggered() if (!cef) return; - config_set_int(App()->GlobalConfig(), "General", "InfoIncrement", -1); + config_set_int(App()->GetUserConfig(), "General", "InfoIncrement", -1); WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); connect(wnit, &WhatsNewInfoThread::Result, this, @@ -8482,22 +8554,26 @@ void OBSBasic::on_actionReleaseNotes_triggered() void OBSBasic::on_actionShowSettingsFolder_triggered() { - char path[512]; - int ret = GetConfigPath(path, 512, "obs-studio"); - if (ret <= 0) - return; + const std::string userConfigPath = + App()->userConfigLocation.u8string() + "/obs-studio"; + const QString userConfigLocation = + QString::fromStdString(userConfigPath); - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); + QDesktopServices::openUrl(QUrl::fromLocalFile(userConfigLocation)); } void OBSBasic::on_actionShowProfileFolder_triggered() { - char path[512]; - int ret = GetProfilePath(path, 512, ""); - if (ret <= 0) - return; + std::string userProfilePath; + userProfilePath.reserve(App()->userProfilesLocation.u8string().size() + + OBSProfilePath.size()); + userProfilePath.append(App()->userProfilesLocation.u8string()) + .append(OBSProfilePath); - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); + const QString userProfileLocation = + QString::fromStdString(userProfilePath); + + QDesktopServices::openUrl(QUrl::fromLocalFile(userProfileLocation)); } int OBSBasic::GetTopSelectedSourceItem() @@ -8584,7 +8660,8 @@ void OBSBasic::ToggleAlwaysOnTop() void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const { - const char *val = config_get_string(basicConfig, "Video", "FPSCommon"); + const char *val = + config_get_string(activeConfiguration, "Video", "FPSCommon"); if (strcmp(val, "10") == 0) { num = 10; @@ -8621,25 +8698,26 @@ void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const void OBSBasic::GetFPSInteger(uint32_t &num, uint32_t &den) const { - num = (uint32_t)config_get_uint(basicConfig, "Video", "FPSInt"); + num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSInt"); den = 1; } void OBSBasic::GetFPSFraction(uint32_t &num, uint32_t &den) const { - num = (uint32_t)config_get_uint(basicConfig, "Video", "FPSNum"); - den = (uint32_t)config_get_uint(basicConfig, "Video", "FPSDen"); + num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNum"); + den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSDen"); } void OBSBasic::GetFPSNanoseconds(uint32_t &num, uint32_t &den) const { num = 1000000000; - den = (uint32_t)config_get_uint(basicConfig, "Video", "FPSNS"); + den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNS"); } void OBSBasic::GetConfigFPS(uint32_t &num, uint32_t &den) const { - uint32_t type = config_get_uint(basicConfig, "Video", "FPSType"); + uint32_t type = + config_get_uint(activeConfiguration, "Video", "FPSType"); if (type == 1) //"Integer" GetFPSInteger(num, den); @@ -8655,7 +8733,7 @@ void OBSBasic::GetConfigFPS(uint32_t &num, uint32_t &den) const config_t *OBSBasic::Config() const { - return basicConfig; + return activeConfiguration; } #ifdef YOUTUBE_ENABLED @@ -9449,7 +9527,8 @@ OBSProjector *OBSBasic::OpenProjector(obs_source_t *source, int monitor, if (monitor > 9 || monitor > QGuiApplication::screens().size() - 1) return nullptr; - bool closeProjectors = config_get_bool(GetGlobalConfig(), "BasicWindow", + bool closeProjectors = config_get_bool(App()->GetUserConfig(), + "BasicWindow", "CloseExistingProjectors"); if (closeProjectors && monitor > -1) { @@ -9604,9 +9683,9 @@ void OBSBasic::UpdateTitleBar() stringstream name; const char *profile = - config_get_string(App()->GlobalConfig(), "Basic", "Profile"); + config_get_string(App()->GetUserConfig(), "Basic", "Profile"); const char *sceneCollection = config_get_string( - App()->GlobalConfig(), "Basic", "SceneCollection"); + App()->GetUserConfig(), "Basic", "SceneCollection"); name << "OBS "; if (previewProgramMode) @@ -9627,8 +9706,8 @@ void OBSBasic::UpdateTitleBar() int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const { char profiles_path[512]; - const char *profile = - config_get_string(App()->GlobalConfig(), "Basic", "ProfileDir"); + const char *profile = config_get_string(App()->GetUserConfig(), "Basic", + "ProfileDir"); int ret; if (!profile) @@ -9638,7 +9717,7 @@ int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const if (!file) file = ""; - ret = GetConfigPath(profiles_path, 512, "obs-studio/basic/profiles"); + ret = GetAppConfigPath(profiles_path, 512, "obs-studio/basic/profiles"); if (ret <= 0) return ret; @@ -9803,7 +9882,7 @@ void OBSBasic::on_resetUI_triggered() ui->scenes->SetGridMode(false); ui->actionSceneListMode->setChecked(true); - config_set_bool(App()->GlobalConfig(), "BasicWindow", "gridMode", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); } @@ -9818,7 +9897,7 @@ void OBSBasic::on_toggleListboxToolbars_toggled(bool visible) ui->scenesToolbar->setVisible(visible); ui->mixerToolbar->setVisible(visible); - config_set_bool(App()->GlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowListboxToolbars", visible); } @@ -9836,7 +9915,7 @@ void OBSBasic::HideContextBar() void OBSBasic::on_toggleContextBar_toggled(bool visible) { - config_set_bool(App()->GlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars", visible); this->ui->contextContainer->setVisible(visible); UpdateContextBar(true); @@ -9846,7 +9925,7 @@ void OBSBasic::on_toggleStatusBar_toggled(bool visible) { ui->statusbar->setVisible(visible); - config_set_bool(App()->GlobalConfig(), "BasicWindow", "ShowStatusBar", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowStatusBar", visible); } @@ -9856,8 +9935,8 @@ void OBSBasic::on_toggleSourceIcons_toggled(bool visible) if (advAudioWindow != nullptr) advAudioWindow->SetIconsVisible(visible); - config_set_bool(App()->GlobalConfig(), "BasicWindow", "ShowSourceIcons", - visible); + config_set_bool(App()->GetUserConfig(), "BasicWindow", + "ShowSourceIcons", visible); } void OBSBasic::on_actionLockPreview_triggered() @@ -9922,7 +10001,7 @@ void OBSBasic::on_actionScaleOutput_triggered() void OBSBasic::SetShowing(bool showing) { if (!showing && isVisible()) { - config_set_string(App()->GlobalConfig(), "BasicWindow", + config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", saveGeometry().toBase64().constData()); @@ -10107,9 +10186,9 @@ void OBSBasic::SystemTray(bool firstStarted) return; bool sysTrayWhenStarted = config_get_bool( - GetGlobalConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool sysTrayEnabled = config_get_bool(GetGlobalConfig(), "BasicWindow", - "SysTrayEnabled"); + App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); + bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), + "BasicWindow", "SysTrayEnabled"); if (firstStarted) SystemTrayInit(); @@ -10135,7 +10214,7 @@ void OBSBasic::SystemTray(bool firstStarted) bool OBSBasic::sysTrayMinimizeToTray() { - return config_get_bool(GetGlobalConfig(), "BasicWindow", + return config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayMinimizeToTray"); } @@ -10573,14 +10652,14 @@ void OBSBasic::ResizeOutputSizeOfSource() int width = obs_source_get_width(source); int height = obs_source_get_height(source); - config_set_uint(basicConfig, "Video", "BaseCX", width); - config_set_uint(basicConfig, "Video", "BaseCY", height); - config_set_uint(basicConfig, "Video", "OutputCX", width); - config_set_uint(basicConfig, "Video", "OutputCY", height); + config_set_uint(activeConfiguration, "Video", "BaseCX", width); + config_set_uint(activeConfiguration, "Video", "BaseCY", height); + config_set_uint(activeConfiguration, "Video", "OutputCX", width); + config_set_uint(activeConfiguration, "Video", "OutputCY", height); ResetVideo(); ResetOutputs(); - config_save_safe(basicConfig, "tmp", nullptr); + activeConfiguration.SaveSafe("tmp"); on_actionFitToScreen_triggered(); } @@ -10913,25 +10992,26 @@ void OBSBasic::RecordPauseToggled() void OBSBasic::UpdateIsRecordingPausable() { - const char *mode = config_get_string(basicConfig, "Output", "Mode"); + const char *mode = + config_get_string(activeConfiguration, "Output", "Mode"); bool adv = astrcmpi(mode, "Advanced") == 0; bool shared = true; if (adv) { - const char *recType = - config_get_string(basicConfig, "AdvOut", "RecType"); + const char *recType = config_get_string(activeConfiguration, + "AdvOut", "RecType"); if (astrcmpi(recType, "FFmpeg") == 0) { - shared = config_get_bool(basicConfig, "AdvOut", + shared = config_get_bool(activeConfiguration, "AdvOut", "FFOutputToFile"); } else { const char *recordEncoder = config_get_string( - basicConfig, "AdvOut", "RecEncoder"); + activeConfiguration, "AdvOut", "RecEncoder"); shared = astrcmpi(recordEncoder, "none") == 0; } } else { const char *quality = config_get_string( - basicConfig, "SimpleOutput", "RecQuality"); + activeConfiguration, "SimpleOutput", "RecQuality"); shared = strcmp(quality, "Stream") == 0; } @@ -11118,17 +11198,17 @@ void OBSBasic::ShowStatusBarMessage(const QString &message) void OBSBasic::UpdatePreviewSafeAreas() { - drawSafeAreas = config_get_bool(App()->GlobalConfig(), "BasicWindow", + drawSafeAreas = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSafeAreas"); } void OBSBasic::UpdatePreviewOverflowSettings() { - bool hidden = config_get_bool(App()->GlobalConfig(), "BasicWindow", + bool hidden = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowHidden"); - bool select = config_get_bool(App()->GlobalConfig(), "BasicWindow", + bool select = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowSelectionHidden"); - bool always = config_get_bool(App()->GlobalConfig(), "BasicWindow", + bool always = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowAlwaysVisible"); ui->preview->SetOverflowHidden(hidden); @@ -11141,7 +11221,7 @@ void OBSBasic::SetDisplayAffinity(QWindow *window) if (!SetDisplayAffinitySupported()) return; - bool hideFromCapture = config_get_bool(App()->GlobalConfig(), + bool hideFromCapture = config_get_bool(App()->GetUserConfig(), "BasicWindow", "HideOBSWindowsFromCapture"); @@ -11175,10 +11255,10 @@ static inline QColor color_from_int(long long val) QColor OBSBasic::GetSelectionColor() const { - if (config_get_bool(GetGlobalConfig(), "Accessibility", + if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { return color_from_int(config_get_int( - GetGlobalConfig(), "Accessibility", "SelectRed")); + App()->GetUserConfig(), "Accessibility", "SelectRed")); } else { return QColor::fromRgb(255, 0, 0); } @@ -11186,10 +11266,11 @@ QColor OBSBasic::GetSelectionColor() const QColor OBSBasic::GetCropColor() const { - if (config_get_bool(GetGlobalConfig(), "Accessibility", + if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int( - GetGlobalConfig(), "Accessibility", "SelectGreen")); + return color_from_int(config_get_int(App()->GetUserConfig(), + "Accessibility", + "SelectGreen")); } else { return QColor::fromRgb(0, 255, 0); } @@ -11197,10 +11278,10 @@ QColor OBSBasic::GetCropColor() const QColor OBSBasic::GetHoverColor() const { - if (config_get_bool(GetGlobalConfig(), "Accessibility", + if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { return color_from_int(config_get_int( - GetGlobalConfig(), "Accessibility", "SelectBlue")); + App()->GetUserConfig(), "Accessibility", "SelectBlue")); } else { return QColor::fromRgb(0, 127, 255); } @@ -11209,7 +11290,7 @@ QColor OBSBasic::GetHoverColor() const void OBSBasic::UpdatePreviewSpacingHelpers() { drawSpacingHelpers = config_get_bool( - App()->GlobalConfig(), "BasicWindow", "SpacingHelpersEnabled"); + App()->GetUserConfig(), "BasicWindow", "SpacingHelpersEnabled"); } float OBSBasic::GetDevicePixelRatio() @@ -11255,3 +11336,57 @@ void OBSBasic::PreviewScalingModeChanged(int value) break; }; } + +// MARK: - Generic UI Helper Functions + +OBSPromptResult OBSBasic::PromptForName(const OBSPromptRequest &request, + const OBSPromptCallback &callback) +{ + OBSPromptResult result; + + for (;;) { + result.success = false; + + if (request.withOption && !request.optionPrompt.empty()) { + result.optionValue = request.optionValue; + + result.success = NameDialog::AskForNameWithOption( + this, request.title.c_str(), + request.prompt.c_str(), result.promptValue, + request.optionPrompt.c_str(), + result.optionValue, + (request.promptValue.empty() + ? nullptr + : request.promptValue.c_str())); + + } else { + result.success = NameDialog::AskForName( + this, request.title.c_str(), + request.prompt.c_str(), result.promptValue, + (request.promptValue.empty() + ? nullptr + : request.promptValue.c_str())); + } + + if (!result.success) { + break; + } + + if (result.promptValue.empty()) { + OBSMessageBox::warning(this, + QTStr("NoNameEntered.Title"), + QTStr("NoNameEntered.Text")); + continue; + } + + if (!callback(result)) { + OBSMessageBox::warning(this, QTStr("NameExists.Title"), + QTStr("NameExists.Text")); + continue; + } + + break; + } + + return result; +} diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp index 3f213483219201..2131e16de93e91 100644 --- a/UI/window-basic-main.hpp +++ b/UI/window-basic-main.hpp @@ -134,6 +134,39 @@ struct QuickTransition { std::shared_ptr renamedSignal; }; +struct OBSProfile { + std::string name; + std::string directoryName; + std::filesystem::path path; + std::filesystem::path profileFile; +}; + +struct OBSSceneCollection { + std::string name; + std::string fileName; + std::filesystem::path collectionFile; +}; + +struct OBSPromptResult { + bool success; + std::string promptValue; + bool optionValue; +}; + +struct OBSPromptRequest { + std::string title; + std::string prompt; + std::string promptValue; + bool withOption; + std::string optionPrompt; + bool optionValue; +}; + +using OBSPromptCallback = std::function; + +using OBSProfileCache = std::map; +using OBSSceneCollectionCache = std::map; + class ColorSelect : public QWidget { public: @@ -308,7 +341,7 @@ class OBSBasic : public OBSMainWindow { int previewCX = 0, previewCY = 0; float previewScale = 0.0f; - ConfigFile basicConfig; + ConfigFile activeConfiguration; std::vector savedProjectorsArray; std::vector projectors; @@ -440,20 +473,7 @@ class OBSBasic : public OBSMainWindow { void ToggleVolControlLayout(); void ToggleMixerLayout(bool vertical); - void RefreshSceneCollections(); - void ChangeSceneCollection(); void LogScenes(); - - void ResetProfileData(); - bool AddProfile(bool create_new, const char *title, const char *text, - const char *init_text = nullptr, bool rename = false); - bool CreateProfile(const std::string &newName, bool create_new, - bool showWizardChecked, bool rename = false); - void DeleteProfile(const char *profile_name, const char *profile_dir); - void RefreshProfiles(); - void ChangeProfile(); - void CheckForSimpleModeX264Fallback(); - void SaveProjectNow(); int GetTopSelectedSourceItem(); @@ -740,13 +760,6 @@ public slots: bool manual = false); void SetCurrentScene(OBSSource scene, bool force = false); - bool AddSceneCollection(bool create_new, - const QString &name = QString()); - - bool NewProfile(const QString &name); - bool DuplicateProfile(const QString &name); - void DeleteProfile(const QString &profileName); - void UpdatePatronJson(const QString &text, const QString &error); void ShowContextBar(); @@ -1148,21 +1161,6 @@ private slots: void ProgramViewContextMenuRequested(); void on_previewDisabledWidget_customContextMenuRequested(); - void on_actionNewSceneCollection_triggered(); - void on_actionDupSceneCollection_triggered(); - void on_actionRenameSceneCollection_triggered(); - void on_actionRemoveSceneCollection_triggered(); - void on_actionImportSceneCollection_triggered(); - void on_actionExportSceneCollection_triggered(); - void on_actionRemigrateSceneCollection_triggered(); - - void on_actionNewProfile_triggered(); - void on_actionDupProfile_triggered(); - void on_actionRenameProfile_triggered(); - void on_actionRemoveProfile_triggered(bool skipConfirmation = false); - void on_actionImportProfile_triggered(); - void on_actionExportProfile_triggered(); - void on_actionShowSettingsFolder_triggered(); void on_actionShowProfileFolder_triggered(); @@ -1337,6 +1335,107 @@ public slots: void DeleteYouTubeAppDock(); YouTubeAppDock *GetYouTubeAppDock(); #endif + // MARK: - Generic UI Helper Functions + OBSPromptResult PromptForName(const OBSPromptRequest &request, + const OBSPromptCallback &callback); + + // MARK: - OBS Profile Management +private: + OBSProfileCache profiles{}; + + void SetupNewProfile(const std::string &profileName, + bool useWizard = false); + void SetupDuplicateProfile(const std::string &profileName); + void SetupRenameProfile(const std::string &profileName); + + const OBSProfile &CreateProfile(const std::string &profileName); + void RemoveProfile(OBSProfile profile); + + void ChangeProfile(); + + void RefreshProfileCache(); + + void RefreshProfiles(bool refreshCache = false); + + void ActivateProfile(const OBSProfile &profile, bool reset = false); + std::vector + GetRestartRequirements(const ConfigFile &config) const; + void ResetProfileData(); + void CheckForSimpleModeX264Fallback(); + +public: + inline const OBSProfileCache &GetProfileCache() const noexcept + { + return profiles; + }; + + const OBSProfile &GetCurrentProfile() const; + + std::optional + GetProfileByName(const std::string &profileName) const; + std::optional + GetProfileByDirectoryName(const std::string &directoryName) const; + +private slots: + void on_actionNewProfile_triggered(); + void on_actionDupProfile_triggered(); + void on_actionRenameProfile_triggered(); + void on_actionRemoveProfile_triggered(bool skipConfirmation = false); + void on_actionImportProfile_triggered(); + void on_actionExportProfile_triggered(); + +public slots: + bool CreateNewProfile(const QString &name); + bool CreateDuplicateProfile(const QString &name); + void DeleteProfile(const QString &profileName); + + // MARK: - OBS Scene Collection Management +private: + OBSSceneCollectionCache collections{}; + + void SetupNewSceneCollection(const std::string &collectionName); + void SetupDuplicateSceneCollection(const std::string &collectionName); + void SetupRenameSceneCollection(const std::string &collectionName); + + const OBSSceneCollection & + CreateSceneCollection(const std::string &collectionName); + void RemoveSceneCollection(OBSSceneCollection collection); + + bool CreateDuplicateSceneCollection(const QString &name); + void DeleteSceneCollection(const QString &name); + void ChangeSceneCollection(); + + void RefreshSceneCollectionCache(); + + void RefreshSceneCollections(bool refreshCache = false); + void ActivateSceneCollection(const OBSSceneCollection &collection); + +public: + inline const OBSSceneCollectionCache & + GetSceneCollectionCache() const noexcept + { + return collections; + }; + + const OBSSceneCollection &GetCurrentSceneCollection() const; + + std::optional + GetSceneCollectionByName(const std::string &collectionName) const; + std::optional + GetSceneCollectionByFileName(const std::string &fileName) const; + +private slots: + void on_actionNewSceneCollection_triggered(); + void on_actionDupSceneCollection_triggered(); + void on_actionRenameSceneCollection_triggered(); + void + on_actionRemoveSceneCollection_triggered(bool skipConfirmation = false); + void on_actionImportSceneCollection_triggered(); + void on_actionExportSceneCollection_triggered(); + void on_actionRemigrateSceneCollection_triggered(); + +public slots: + bool CreateNewSceneCollection(const QString &name); }; extern bool cef_js_avail; diff --git a/UI/window-basic-preview.cpp b/UI/window-basic-preview.cpp index 9778169ad10eef..6806150c054171 100644 --- a/UI/window-basic-preview.cpp +++ b/UI/window-basic-preview.cpp @@ -192,17 +192,17 @@ vec3 OBSBasicPreview::GetSnapOffset(const vec3 &tl, const vec3 &br) vec3_zero(&clampOffset); - const bool snap = config_get_bool(GetGlobalConfig(), "BasicWindow", + const bool snap = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SnappingEnabled"); if (snap == false) return clampOffset; const bool screenSnap = config_get_bool( - GetGlobalConfig(), "BasicWindow", "ScreenSnapping"); + App()->GetUserConfig(), "BasicWindow", "ScreenSnapping"); const bool centerSnap = config_get_bool( - GetGlobalConfig(), "BasicWindow", "CenterSnapping"); + App()->GetUserConfig(), "BasicWindow", "CenterSnapping"); - const float clampDist = config_get_double(GetGlobalConfig(), + const float clampDist = config_get_double(App()->GetUserConfig(), "BasicWindow", "SnapDistance") / main->previewScale; @@ -995,10 +995,10 @@ void OBSBasicPreview::SnapItemMovement(vec2 &offset) vec3 snapOffset = GetSnapOffset(data.tl, data.br); - const bool snap = config_get_bool(GetGlobalConfig(), "BasicWindow", + const bool snap = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SnappingEnabled"); const bool sourcesSnap = config_get_bool( - GetGlobalConfig(), "BasicWindow", "SourceSnapping"); + App()->GetUserConfig(), "BasicWindow", "SourceSnapping"); if (snap == false) return; if (sourcesSnap == false) { @@ -1007,7 +1007,7 @@ void OBSBasicPreview::SnapItemMovement(vec2 &offset) return; } - const float clampDist = config_get_double(GetGlobalConfig(), + const float clampDist = config_get_double(App()->GetUserConfig(), "BasicWindow", "SnapDistance") / main->previewScale; diff --git a/UI/window-basic-properties.cpp b/UI/window-basic-properties.cpp index 47703418a2c8c1..e9decdc852bae4 100644 --- a/UI/window-basic-properties.cpp +++ b/UI/window-basic-properties.cpp @@ -53,9 +53,9 @@ OBSBasicProperties::OBSBasicProperties(QWidget *parent, OBSSource source_) OBSBasicProperties::SourceRenamed, this), oldSettings(obs_data_create()) { - int cx = (int)config_get_int(App()->GlobalConfig(), "PropertiesWindow", + int cx = (int)config_get_int(App()->GetAppConfig(), "PropertiesWindow", "cx"); - int cy = (int)config_get_int(App()->GlobalConfig(), "PropertiesWindow", + int cy = (int)config_get_int(App()->GetAppConfig(), "PropertiesWindow", "cy"); enum obs_source_type type = obs_source_get_type(source); @@ -450,9 +450,9 @@ void OBSBasicProperties::DrawTransitionPreview(void *data, uint32_t cx, void OBSBasicProperties::Cleanup() { - config_set_int(App()->GlobalConfig(), "PropertiesWindow", "cx", + config_set_int(App()->GetAppConfig(), "PropertiesWindow", "cx", width()); - config_set_int(App()->GlobalConfig(), "PropertiesWindow", "cy", + config_set_int(App()->GetAppConfig(), "PropertiesWindow", "cy", height()); obs_display_remove_draw_callback(ui->preview->GetDisplay(), diff --git a/UI/window-basic-settings-a11y.cpp b/UI/window-basic-settings-a11y.cpp index 2437b2c11de0f8..2cce25ba6c6bf7 100644 --- a/UI/window-basic-settings-a11y.cpp +++ b/UI/window-basic-settings-a11y.cpp @@ -43,7 +43,7 @@ QColor OBSBasicSettings::GetColor(uint32_t colorVal, QString label) void OBSBasicSettings::LoadA11ySettings(bool presetChange) { - config_t *config = GetGlobalConfig(); + config_t *config = App()->GetUserConfig(); loading = true; if (!presetChange) { @@ -109,7 +109,7 @@ void OBSBasicSettings::LoadA11ySettings(bool presetChange) void OBSBasicSettings::SaveA11ySettings() { - config_t *config = GetGlobalConfig(); + config_t *config = App()->GetUserConfig(); config_set_bool(config, "Accessibility", "OverrideColors", ui->colorsGroupBox->isChecked()); @@ -163,7 +163,7 @@ void OBSBasicSettings::UpdateA11yColors() void OBSBasicSettings::SetDefaultColors() { - config_t *config = GetGlobalConfig(); + config_t *config = App()->GetUserConfig(); config_set_default_int(config, "Accessibility", "SelectRed", selectRed); config_set_default_int(config, "Accessibility", "SelectGreen", selectGreen); diff --git a/UI/window-basic-settings-appearance.cpp b/UI/window-basic-settings-appearance.cpp index 58c8401b6f0be4..20614c4b8ec67c 100644 --- a/UI/window-basic-settings-appearance.cpp +++ b/UI/window-basic-settings-appearance.cpp @@ -107,7 +107,7 @@ void OBSBasicSettings::LoadAppearanceSettings(bool reload) void OBSBasicSettings::SaveAppearanceSettings() { - config_t *config = GetGlobalConfig(); + config_t *config = App()->GetUserConfig(); OBSTheme *currentTheme = App()->GetTheme(); if (savedTheme != currentTheme) { diff --git a/UI/window-basic-settings-stream.cpp b/UI/window-basic-settings-stream.cpp index f005faf8f7dbe5..d0741a0a7f7b24 100644 --- a/UI/window-basic-settings-stream.cpp +++ b/UI/window-basic-settings-stream.cpp @@ -1032,8 +1032,9 @@ void OBSBasicSettings::on_server_currentIndexChanged(int /*index*/) void OBSBasicSettings::UpdateVodTrackSetting() { - bool enableForCustomServer = config_get_bool( - GetGlobalConfig(), "General", "EnableCustomServerVodTrack"); + bool enableForCustomServer = + config_get_bool(App()->GetUserConfig(), "General", + "EnableCustomServerVodTrack"); bool enableVodTrack = ui->service->currentText() == "Twitch"; bool wasEnabled = !!vodTrackCheckbox; diff --git a/UI/window-basic-settings.cpp b/UI/window-basic-settings.cpp index b85a53ad608c49..9244050777c79e 100644 --- a/UI/window-basic-settings.cpp +++ b/UI/window-basic-settings.cpp @@ -1333,8 +1333,8 @@ void OBSBasicSettings::LoadBranchesList() { #if defined(_WIN32) || defined(ENABLE_SPARKLE_UPDATER) bool configBranchRemoved = true; - QString configBranch = - config_get_string(GetGlobalConfig(), "General", "UpdateBranch"); + QString configBranch = config_get_string(App()->GetAppConfig(), + "General", "UpdateBranch"); for (const UpdateBranch &branch : App()->GetBranches()) { if (branch.name == configBranch) @@ -1387,8 +1387,8 @@ void OBSBasicSettings::LoadGeneralSettings() LoadLanguageList(); #if defined(_WIN32) || defined(ENABLE_SPARKLE_UPDATER) - bool enableAutoUpdates = config_get_bool(GetGlobalConfig(), "General", - "EnableAutoUpdates"); + bool enableAutoUpdates = config_get_bool( + App()->GetUserConfig(), "General", "EnableAutoUpdates"); ui->enableAutoUpdates->setChecked(enableAutoUpdates); LoadBranchesList(); @@ -1400,7 +1400,7 @@ void OBSBasicSettings::LoadGeneralSettings() #if defined(_WIN32) if (ui->hideOBSFromCapture) { bool hideWindowFromCapture = - config_get_bool(GetGlobalConfig(), "BasicWindow", + config_get_bool(App()->GetUserConfig(), "BasicWindow", "HideOBSWindowsFromCapture"); ui->hideOBSFromCapture->setChecked(hideWindowFromCapture); @@ -1415,129 +1415,136 @@ void OBSBasicSettings::LoadGeneralSettings() #endif bool recordWhenStreaming = config_get_bool( - GetGlobalConfig(), "BasicWindow", "RecordWhenStreaming"); + App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); ui->recordWhenStreaming->setChecked(recordWhenStreaming); bool keepRecordStreamStops = - config_get_bool(GetGlobalConfig(), "BasicWindow", + config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); ui->keepRecordStreamStops->setChecked(keepRecordStreamStops); - bool replayWhileStreaming = config_get_bool( - GetGlobalConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); + bool replayWhileStreaming = + config_get_bool(App()->GetUserConfig(), "BasicWindow", + "ReplayBufferWhileStreaming"); ui->replayWhileStreaming->setChecked(replayWhileStreaming); bool keepReplayStreamStops = - config_get_bool(GetGlobalConfig(), "BasicWindow", + config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); ui->keepReplayStreamStops->setChecked(keepReplayStreamStops); bool systemTrayEnabled = config_get_bool( - GetGlobalConfig(), "BasicWindow", "SysTrayEnabled"); + App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); ui->systemTrayEnabled->setChecked(systemTrayEnabled); bool systemTrayWhenStarted = config_get_bool( - GetGlobalConfig(), "BasicWindow", "SysTrayWhenStarted"); + App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); ui->systemTrayWhenStarted->setChecked(systemTrayWhenStarted); bool systemTrayAlways = config_get_bool( - GetGlobalConfig(), "BasicWindow", "SysTrayMinimizeToTray"); + App()->GetUserConfig(), "BasicWindow", "SysTrayMinimizeToTray"); ui->systemTrayAlways->setChecked(systemTrayAlways); - bool saveProjectors = config_get_bool(GetGlobalConfig(), "BasicWindow", - "SaveProjectors"); + bool saveProjectors = config_get_bool(App()->GetUserConfig(), + "BasicWindow", "SaveProjectors"); ui->saveProjectors->setChecked(saveProjectors); - bool closeProjectors = config_get_bool(GetGlobalConfig(), "BasicWindow", + bool closeProjectors = config_get_bool(App()->GetUserConfig(), + "BasicWindow", "CloseExistingProjectors"); ui->closeProjectors->setChecked(closeProjectors); - bool snappingEnabled = config_get_bool(GetGlobalConfig(), "BasicWindow", - "SnappingEnabled"); + bool snappingEnabled = config_get_bool( + App()->GetUserConfig(), "BasicWindow", "SnappingEnabled"); ui->snappingEnabled->setChecked(snappingEnabled); - bool screenSnapping = config_get_bool(GetGlobalConfig(), "BasicWindow", - "ScreenSnapping"); + bool screenSnapping = config_get_bool(App()->GetUserConfig(), + "BasicWindow", "ScreenSnapping"); ui->screenSnapping->setChecked(screenSnapping); - bool centerSnapping = config_get_bool(GetGlobalConfig(), "BasicWindow", - "CenterSnapping"); + bool centerSnapping = config_get_bool(App()->GetUserConfig(), + "BasicWindow", "CenterSnapping"); ui->centerSnapping->setChecked(centerSnapping); - bool sourceSnapping = config_get_bool(GetGlobalConfig(), "BasicWindow", - "SourceSnapping"); + bool sourceSnapping = config_get_bool(App()->GetUserConfig(), + "BasicWindow", "SourceSnapping"); ui->sourceSnapping->setChecked(sourceSnapping); - double snapDistance = config_get_double(GetGlobalConfig(), + double snapDistance = config_get_double(App()->GetUserConfig(), "BasicWindow", "SnapDistance"); ui->snapDistance->setValue(snapDistance); - bool warnBeforeStreamStart = config_get_bool( - GetGlobalConfig(), "BasicWindow", "WarnBeforeStartingStream"); + bool warnBeforeStreamStart = + config_get_bool(App()->GetUserConfig(), "BasicWindow", + "WarnBeforeStartingStream"); ui->warnBeforeStreamStart->setChecked(warnBeforeStreamStart); bool spacingHelpersEnabled = config_get_bool( - GetGlobalConfig(), "BasicWindow", "SpacingHelpersEnabled"); + App()->GetUserConfig(), "BasicWindow", "SpacingHelpersEnabled"); ui->previewSpacingHelpers->setChecked(spacingHelpersEnabled); - bool warnBeforeStreamStop = config_get_bool( - GetGlobalConfig(), "BasicWindow", "WarnBeforeStoppingStream"); + bool warnBeforeStreamStop = config_get_bool(App()->GetUserConfig(), + "BasicWindow", + "WarnBeforeStoppingStream"); ui->warnBeforeStreamStop->setChecked(warnBeforeStreamStop); - bool warnBeforeRecordStop = config_get_bool( - GetGlobalConfig(), "BasicWindow", "WarnBeforeStoppingRecord"); + bool warnBeforeRecordStop = config_get_bool(App()->GetUserConfig(), + "BasicWindow", + "WarnBeforeStoppingRecord"); ui->warnBeforeRecordStop->setChecked(warnBeforeRecordStop); bool hideProjectorCursor = config_get_bool( - GetGlobalConfig(), "BasicWindow", "HideProjectorCursor"); + App()->GetUserConfig(), "BasicWindow", "HideProjectorCursor"); ui->hideProjectorCursor->setChecked(hideProjectorCursor); bool projectorAlwaysOnTop = config_get_bool( - GetGlobalConfig(), "BasicWindow", "ProjectorAlwaysOnTop"); + App()->GetUserConfig(), "BasicWindow", "ProjectorAlwaysOnTop"); ui->projectorAlwaysOnTop->setChecked(projectorAlwaysOnTop); - bool overflowHide = config_get_bool(GetGlobalConfig(), "BasicWindow", - "OverflowHidden"); + bool overflowHide = config_get_bool(App()->GetUserConfig(), + "BasicWindow", "OverflowHidden"); ui->overflowHide->setChecked(overflowHide); bool overflowAlwaysVisible = config_get_bool( - GetGlobalConfig(), "BasicWindow", "OverflowAlwaysVisible"); + App()->GetUserConfig(), "BasicWindow", "OverflowAlwaysVisible"); ui->overflowAlwaysVisible->setChecked(overflowAlwaysVisible); - bool overflowSelectionHide = config_get_bool( - GetGlobalConfig(), "BasicWindow", "OverflowSelectionHidden"); + bool overflowSelectionHide = config_get_bool(App()->GetUserConfig(), + "BasicWindow", + "OverflowSelectionHidden"); ui->overflowSelectionHide->setChecked(overflowSelectionHide); - bool safeAreas = config_get_bool(GetGlobalConfig(), "BasicWindow", + bool safeAreas = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSafeAreas"); ui->previewSafeAreas->setChecked(safeAreas); - bool automaticSearch = config_get_bool(GetGlobalConfig(), "General", - "AutomaticCollectionSearch"); + bool automaticSearch = config_get_bool( + App()->GetUserConfig(), "General", "AutomaticCollectionSearch"); ui->automaticSearch->setChecked(automaticSearch); - bool doubleClickSwitch = config_get_bool( - GetGlobalConfig(), "BasicWindow", "TransitionOnDoubleClick"); + bool doubleClickSwitch = config_get_bool(App()->GetUserConfig(), + "BasicWindow", + "TransitionOnDoubleClick"); ui->doubleClickSwitch->setChecked(doubleClickSwitch); bool studioPortraitLayout = config_get_bool( - GetGlobalConfig(), "BasicWindow", "StudioPortraitLayout"); + App()->GetUserConfig(), "BasicWindow", "StudioPortraitLayout"); ui->studioPortraitLayout->setChecked(studioPortraitLayout); - bool prevProgLabels = config_get_bool(GetGlobalConfig(), "BasicWindow", - "StudioModeLabels"); + bool prevProgLabels = config_get_bool( + App()->GetUserConfig(), "BasicWindow", "StudioModeLabels"); ui->prevProgLabelToggle->setChecked(prevProgLabels); bool multiviewMouseSwitch = config_get_bool( - GetGlobalConfig(), "BasicWindow", "MultiviewMouseSwitch"); + App()->GetUserConfig(), "BasicWindow", "MultiviewMouseSwitch"); ui->multiviewMouseSwitch->setChecked(multiviewMouseSwitch); bool multiviewDrawNames = config_get_bool( - GetGlobalConfig(), "BasicWindow", "MultiviewDrawNames"); + App()->GetUserConfig(), "BasicWindow", "MultiviewDrawNames"); ui->multiviewDrawNames->setChecked(multiviewDrawNames); bool multiviewDrawAreas = config_get_bool( - GetGlobalConfig(), "BasicWindow", "MultiviewDrawAreas"); + App()->GetUserConfig(), "BasicWindow", "MultiviewDrawAreas"); ui->multiviewDrawAreas->setChecked(multiviewDrawAreas); ui->multiviewLayout->addItem( @@ -1571,9 +1578,10 @@ void OBSBasicSettings::LoadGeneralSettings() QTStr("Basic.Settings.General.MultiviewLayout.25Scene"), static_cast(MultiviewLayout::SCENES_ONLY_25_SCENES)); - ui->multiviewLayout->setCurrentIndex(ui->multiviewLayout->findData( - QVariant::fromValue(config_get_int( - GetGlobalConfig(), "BasicWindow", "MultiviewLayout")))); + ui->multiviewLayout->setCurrentIndex( + ui->multiviewLayout->findData(QVariant::fromValue( + config_get_int(App()->GetUserConfig(), "BasicWindow", + "MultiviewLayout")))); prevLangIndex = ui->language->currentIndex(); @@ -1587,7 +1595,7 @@ void OBSBasicSettings::LoadRendererList() { #ifdef _WIN32 const char *renderer = - config_get_string(GetGlobalConfig(), "Video", "Renderer"); + config_get_string(App()->GetUserConfig(), "Video", "Renderer"); ui->renderer->addItem(QT_UTF8("Direct3D 11")); if (opt_allow_opengl || strcmp(renderer, "OpenGL") == 0) @@ -2128,12 +2136,16 @@ OBSBasicSettings::CreateEncoderPropertyView(const char *encoder, OBSPropertiesView *view; if (path) { - char encoderJsonPath[512]; - int ret = GetProfilePath(encoderJsonPath, - sizeof(encoderJsonPath), path); - if (ret > 0) { + const OBSBasic *basic = + reinterpret_cast(App()->GetMainWindow()); + const OBSProfile ¤tProfile = basic->GetCurrentProfile(); + + const std::filesystem::path jsonFilePath = + currentProfile.path / std::filesystem::u8path(path); + + if (!jsonFilePath.empty()) { obs_data_t *data = obs_data_create_from_json_file_safe( - encoderJsonPath, "bak"); + jsonFilePath.u8string().c_str(), "bak"); obs_data_apply(settings, data); obs_data_release(data); } @@ -2792,7 +2804,7 @@ void OBSBasicSettings::LoadAudioSettings() uint32_t peakMeterTypeIdx = config_get_uint(main->Config(), "Audio", "PeakMeterType"); bool enableLLAudioBuffering = config_get_bool( - GetGlobalConfig(), "Audio", "LowLatencyAudioBuffering"); + App()->GetUserConfig(), "Audio", "LowLatencyAudioBuffering"); loading = true; @@ -2918,13 +2930,13 @@ void OBSBasicSettings::LoadAdvancedSettings() int rbSize = config_get_int(main->Config(), "AdvOut", "RecRBSize"); bool autoRemux = config_get_bool(main->Config(), "Video", "AutoRemux"); const char *hotkeyFocusType = config_get_string( - App()->GlobalConfig(), "General", "HotkeyFocusType"); + App()->GetUserConfig(), "General", "HotkeyFocusType"); bool dynBitrate = config_get_bool(main->Config(), "Output", "DynamicBitrate"); const char *ipFamily = config_get_string(main->Config(), "Output", "IPFamily"); - bool confirmOnExit = - config_get_bool(GetGlobalConfig(), "General", "ConfirmOnExit"); + bool confirmOnExit = config_get_bool(App()->GetUserConfig(), "General", + "ConfirmOnExit"); loading = true; @@ -2971,20 +2983,20 @@ void OBSBasicSettings::LoadAdvancedSettings() } #ifdef __APPLE__ - bool disableOSXVSync = config_get_bool(App()->GlobalConfig(), "Video", + bool disableOSXVSync = config_get_bool(App()->GetUserConfig(), "Video", "DisableOSXVSync"); - bool resetOSXVSync = config_get_bool(App()->GlobalConfig(), "Video", + bool resetOSXVSync = config_get_bool(App()->GetUserConfig(), "Video", "ResetOSXVSyncOnExit"); ui->disableOSXVSync->setChecked(disableOSXVSync); ui->resetOSXVSync->setChecked(resetOSXVSync); ui->resetOSXVSync->setEnabled(disableOSXVSync); #elif _WIN32 bool disableAudioDucking = config_get_bool( - App()->GlobalConfig(), "Audio", "DisableAudioDucking"); + App()->GetUserConfig(), "Audio", "DisableAudioDucking"); ui->disableAudioDucking->setChecked(disableAudioDucking); const char *processPriority = config_get_string( - App()->GlobalConfig(), "General", "ProcessPriority"); + App()->GetAppConfig(), "General", "ProcessPriority"); bool enableNewSocketLoop = config_get_bool(main->Config(), "Output", "NewSocketLoopEnable"); bool enableLowLatencyMode = @@ -3001,7 +3013,7 @@ void OBSBasicSettings::LoadAdvancedSettings() QTStr("Basic.Settings.Advanced.Network.TCPPacing.Tooltip")); #endif #if defined(_WIN32) || defined(__APPLE__) - bool browserHWAccel = config_get_bool(App()->GlobalConfig(), "General", + bool browserHWAccel = config_get_bool(App()->GetUserConfig(), "General", "BrowserHWAccel"); ui->browserHWAccel->setChecked(browserHWAccel); prevBrowserAccel = ui->browserHWAccel->isChecked(); @@ -3360,12 +3372,12 @@ void OBSBasicSettings::SaveGeneralSettings() string language = langData.toString().toStdString(); if (WidgetChanged(ui->language)) - config_set_string(GetGlobalConfig(), "General", "Language", + config_set_string(App()->GetUserConfig(), "General", "Language", language.c_str()); #if defined(_WIN32) || defined(ENABLE_SPARKLE_UPDATER) if (WidgetChanged(ui->enableAutoUpdates)) - config_set_bool(GetGlobalConfig(), "General", + config_set_bool(App()->GetUserConfig(), "General", "EnableAutoUpdates", ui->enableAutoUpdates->isChecked()); int branchIdx = ui->updateChannelBox->currentIndex(); @@ -3373,15 +3385,15 @@ void OBSBasicSettings::SaveGeneralSettings() ui->updateChannelBox->itemData(branchIdx).toString(); if (WidgetChanged(ui->updateChannelBox)) { - config_set_string(GetGlobalConfig(), "General", "UpdateBranch", - QT_TO_UTF8(branchName)); + config_set_string(App()->GetAppConfig(), "General", + "UpdateBranch", QT_TO_UTF8(branchName)); forceUpdateCheck = true; } #endif #ifdef _WIN32 if (ui->hideOBSFromCapture && WidgetChanged(ui->hideOBSFromCapture)) { bool hide_window = ui->hideOBSFromCapture->isChecked(); - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "HideOBSWindowsFromCapture", hide_window); QWindowList windows = QGuiApplication::allWindows(); @@ -3399,80 +3411,80 @@ void OBSBasicSettings::SaveGeneralSettings() config_set_bool(main->Config(), "General", "OpenStatsOnStartup", ui->openStatsOnStartup->isChecked()); if (WidgetChanged(ui->snappingEnabled)) - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "SnappingEnabled", ui->snappingEnabled->isChecked()); if (WidgetChanged(ui->screenSnapping)) - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "ScreenSnapping", ui->screenSnapping->isChecked()); if (WidgetChanged(ui->centerSnapping)) - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "CenterSnapping", ui->centerSnapping->isChecked()); if (WidgetChanged(ui->sourceSnapping)) - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "SourceSnapping", ui->sourceSnapping->isChecked()); if (WidgetChanged(ui->snapDistance)) - config_set_double(GetGlobalConfig(), "BasicWindow", + config_set_double(App()->GetUserConfig(), "BasicWindow", "SnapDistance", ui->snapDistance->value()); if (WidgetChanged(ui->overflowAlwaysVisible) || WidgetChanged(ui->overflowHide) || WidgetChanged(ui->overflowSelectionHide)) { - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "OverflowAlwaysVisible", ui->overflowAlwaysVisible->isChecked()); - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "OverflowHidden", ui->overflowHide->isChecked()); - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "OverflowSelectionHidden", ui->overflowSelectionHide->isChecked()); main->UpdatePreviewOverflowSettings(); } if (WidgetChanged(ui->previewSafeAreas)) { - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowSafeAreas", ui->previewSafeAreas->isChecked()); main->UpdatePreviewSafeAreas(); } if (WidgetChanged(ui->previewSpacingHelpers)) { - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "SpacingHelpersEnabled", ui->previewSpacingHelpers->isChecked()); main->UpdatePreviewSpacingHelpers(); } if (WidgetChanged(ui->doubleClickSwitch)) - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "TransitionOnDoubleClick", ui->doubleClickSwitch->isChecked()); if (WidgetChanged(ui->automaticSearch)) - config_set_bool(GetGlobalConfig(), "General", + config_set_bool(App()->GetUserConfig(), "General", "AutomaticCollectionSearch", ui->automaticSearch->isChecked()); - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStartingStream", ui->warnBeforeStreamStart->isChecked()); - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream", ui->warnBeforeStreamStop->isChecked()); - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingRecord", ui->warnBeforeRecordStop->isChecked()); if (WidgetChanged(ui->hideProjectorCursor)) { - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "HideProjectorCursor", ui->hideProjectorCursor->isChecked()); main->UpdateProjectorHideCursor(); } if (WidgetChanged(ui->projectorAlwaysOnTop)) { - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "ProjectorAlwaysOnTop", ui->projectorAlwaysOnTop->isChecked()); #if defined(_WIN32) || defined(__APPLE__) @@ -3484,25 +3496,25 @@ void OBSBasicSettings::SaveGeneralSettings() } if (WidgetChanged(ui->recordWhenStreaming)) - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming", ui->recordWhenStreaming->isChecked()); if (WidgetChanged(ui->keepRecordStreamStops)) - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops", ui->keepRecordStreamStops->isChecked()); if (WidgetChanged(ui->replayWhileStreaming)) - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming", ui->replayWhileStreaming->isChecked()); if (WidgetChanged(ui->keepReplayStreamStops)) - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops", ui->keepReplayStreamStops->isChecked()); if (WidgetChanged(ui->systemTrayEnabled)) { - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled", ui->systemTrayEnabled->isChecked()); @@ -3510,27 +3522,27 @@ void OBSBasicSettings::SaveGeneralSettings() } if (WidgetChanged(ui->systemTrayWhenStarted)) - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted", ui->systemTrayWhenStarted->isChecked()); if (WidgetChanged(ui->systemTrayAlways)) - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayMinimizeToTray", ui->systemTrayAlways->isChecked()); if (WidgetChanged(ui->saveProjectors)) - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "SaveProjectors", ui->saveProjectors->isChecked()); if (WidgetChanged(ui->closeProjectors)) - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "CloseExistingProjectors", ui->closeProjectors->isChecked()); if (WidgetChanged(ui->studioPortraitLayout)) { - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "StudioPortraitLayout", ui->studioPortraitLayout->isChecked()); @@ -3538,7 +3550,7 @@ void OBSBasicSettings::SaveGeneralSettings() } if (WidgetChanged(ui->prevProgLabelToggle)) { - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "StudioModeLabels", ui->prevProgLabelToggle->isChecked()); @@ -3547,28 +3559,28 @@ void OBSBasicSettings::SaveGeneralSettings() bool multiviewChanged = false; if (WidgetChanged(ui->multiviewMouseSwitch)) { - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "MultiviewMouseSwitch", ui->multiviewMouseSwitch->isChecked()); multiviewChanged = true; } if (WidgetChanged(ui->multiviewDrawNames)) { - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "MultiviewDrawNames", ui->multiviewDrawNames->isChecked()); multiviewChanged = true; } if (WidgetChanged(ui->multiviewDrawAreas)) { - config_set_bool(GetGlobalConfig(), "BasicWindow", + config_set_bool(App()->GetUserConfig(), "BasicWindow", "MultiviewDrawAreas", ui->multiviewDrawAreas->isChecked()); multiviewChanged = true; } if (WidgetChanged(ui->multiviewLayout)) { - config_set_int(GetGlobalConfig(), "BasicWindow", + config_set_int(App()->GetUserConfig(), "BasicWindow", "MultiviewLayout", ui->multiviewLayout->currentData().toInt()); multiviewChanged = true; @@ -3616,12 +3628,12 @@ void OBSBasicSettings::SaveAdvancedSettings() #ifdef _WIN32 if (WidgetChanged(ui->renderer)) - config_set_string(App()->GlobalConfig(), "Video", "Renderer", + config_set_string(App()->GetUserConfig(), "Video", "Renderer", QT_TO_UTF8(ui->renderer->currentText())); std::string priority = QT_TO_UTF8(ui->processPriority->currentData().toString()); - config_set_string(App()->GlobalConfig(), "General", "ProcessPriority", + config_set_string(App()->GetAppConfig(), "General", "ProcessPriority", priority.c_str()); if (main->Active()) SetProcessPriority(priority.c_str()); @@ -3631,25 +3643,25 @@ void OBSBasicSettings::SaveAdvancedSettings() #endif #if defined(_WIN32) || defined(__APPLE__) bool browserHWAccel = ui->browserHWAccel->isChecked(); - config_set_bool(App()->GlobalConfig(), "General", "BrowserHWAccel", + config_set_bool(App()->GetUserConfig(), "General", "BrowserHWAccel", browserHWAccel); #endif if (WidgetChanged(ui->hotkeyFocusType)) { QString str = GetComboData(ui->hotkeyFocusType); - config_set_string(App()->GlobalConfig(), "General", + config_set_string(App()->GetUserConfig(), "General", "HotkeyFocusType", QT_TO_UTF8(str)); } #ifdef __APPLE__ if (WidgetChanged(ui->disableOSXVSync)) { bool disable = ui->disableOSXVSync->isChecked(); - config_set_bool(App()->GlobalConfig(), "Video", + config_set_bool(App()->GetUserConfig(), "Video", "DisableOSXVSync", disable); EnableOSXVSync(!disable); } if (WidgetChanged(ui->resetOSXVSync)) - config_set_bool(App()->GlobalConfig(), "Video", + config_set_bool(App()->GetUserConfig(), "Video", "ResetOSXVSyncOnExit", ui->resetOSXVSync->isChecked()); #endif @@ -3669,14 +3681,15 @@ void OBSBasicSettings::SaveAdvancedSettings() #ifdef _WIN32 if (WidgetChanged(ui->disableAudioDucking)) { bool disable = ui->disableAudioDucking->isChecked(); - config_set_bool(App()->GlobalConfig(), "Audio", + config_set_bool(App()->GetUserConfig(), "Audio", "DisableAudioDucking", disable); DisableAudioDucking(disable); } #endif if (WidgetChanged(ui->confirmOnExit)) - config_set_bool(GetGlobalConfig(), "General", "ConfirmOnExit", + config_set_bool(App()->GetUserConfig(), "General", + "ConfirmOnExit", ui->confirmOnExit->isChecked()); SaveEdit(ui->filenameFormatting, "Output", "FilenameFormatting"); @@ -3739,17 +3752,22 @@ static inline const char *SplitFileTypeFromIdx(int idx) static void WriteJsonData(OBSPropertiesView *view, const char *path) { - char full_path[512]; - if (!view || !WidgetChanged(view)) return; - int ret = GetProfilePath(full_path, sizeof(full_path), path); - if (ret > 0) { + const OBSBasic *basic = + reinterpret_cast(App()->GetMainWindow()); + const OBSProfile ¤tProfile = basic->GetCurrentProfile(); + + const std::filesystem::path jsonFilePath = + currentProfile.path / std::filesystem::u8path(path); + + if (!jsonFilePath.empty()) { obs_data_t *settings = view->GetSettings(); if (settings) { - obs_data_save_json_safe(settings, full_path, "tmp", - "bak"); + obs_data_save_json_safe(settings, + jsonFilePath.u8string().c_str(), + "tmp", "bak"); } } } @@ -4049,7 +4067,7 @@ void OBSBasicSettings::SaveAudioSettings() if (WidgetChanged(ui->lowLatencyBuffering)) { bool enableLLAudioBuffering = ui->lowLatencyBuffering->isChecked(); - config_set_bool(GetGlobalConfig(), "Audio", + config_set_bool(App()->GetUserConfig(), "Audio", "LowLatencyAudioBuffering", enableLLAudioBuffering); } @@ -4159,7 +4177,7 @@ void OBSBasicSettings::SaveSettings() main->ResetVideo(); config_save_safe(main->Config(), "tmp", nullptr); - config_save_safe(GetGlobalConfig(), "tmp", nullptr); + config_save_safe(App()->GetUserConfig(), "tmp", nullptr); main->SaveProject(); if (Changed()) { @@ -4788,7 +4806,7 @@ void OBSBasicSettings::HideOBSWindowWarning(int state) if (loading || state == Qt::Unchecked) return; - if (config_get_bool(GetGlobalConfig(), "General", + if (config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutHideOBSFromCapture")) return; @@ -4796,9 +4814,9 @@ void OBSBasicSettings::HideOBSWindowWarning(int state) this, QTStr("Basic.Settings.General.HideOBSWindowsFromCapture"), QTStr("Basic.Settings.General.HideOBSWindowsFromCapture.Message")); - config_set_bool(GetGlobalConfig(), "General", + config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutHideOBSFromCapture", true); - config_save_safe(GetGlobalConfig(), "tmp", nullptr); + config_save_safe(App()->GetUserConfig(), "tmp", nullptr); } /* @@ -5682,14 +5700,16 @@ void OBSBasicSettings::AdvReplayBufferChanged() if (!settings) return; - char encoderJsonPath[512]; - int ret = GetProfilePath(encoderJsonPath, - sizeof(encoderJsonPath), - "recordEncoder.json"); - if (ret > 0) { + const OBSProfile ¤tProfile = main->GetCurrentProfile(); + + const std::filesystem::path jsonFilePath = + currentProfile.path / + std::filesystem::u8path("recordEncoder.json"); + + if (!jsonFilePath.empty()) { OBSDataAutoRelease data = obs_data_create_from_json_file_safe( - encoderJsonPath, "bak"); + jsonFilePath.u8string().c_str(), "bak"); obs_data_apply(settings, data); } } diff --git a/UI/window-dock-youtube-app.cpp b/UI/window-dock-youtube-app.cpp index 498a68a057597d..888c329ce2cf56 100644 --- a/UI/window-dock-youtube-app.cpp +++ b/UI/window-dock-youtube-app.cpp @@ -431,16 +431,16 @@ void YouTubeAppDock::CleanupYouTubeUrls() // remove legacy YouTube Browser Docks (once) bool youtube_cleanup_done = config_get_bool( - App()->GlobalConfig(), "General", "YtDockCleanupDone"); + App()->GetUserConfig(), "General", "YtDockCleanupDone"); if (youtube_cleanup_done) return; - config_set_bool(App()->GlobalConfig(), "General", "YtDockCleanupDone", + config_set_bool(App()->GetUserConfig(), "General", "YtDockCleanupDone", true); const char *jsonStr = config_get_string( - App()->GlobalConfig(), "BasicWindow", "ExtraBrowserDocks"); + App()->GetUserConfig(), "BasicWindow", "ExtraBrowserDocks"); if (!jsonStr) return; @@ -472,7 +472,7 @@ void YouTubeAppDock::CleanupYouTubeUrls() OBSMessageBox::warning(OBSBasic::Get(), msg_title, msg_text); std::string output = save_array.dump(); - config_set_string(App()->GlobalConfig(), "BasicWindow", + config_set_string(App()->GetUserConfig(), "BasicWindow", "ExtraBrowserDocks", output.c_str()); } } diff --git a/UI/window-dock.cpp b/UI/window-dock.cpp index 7e2aeff89e9e39..37a9b3dd9ce8fa 100644 --- a/UI/window-dock.cpp +++ b/UI/window-dock.cpp @@ -20,13 +20,14 @@ void OBSDock::closeEvent(QCloseEvent *event) msgbox.exec(); if (cb->isChecked()) { - config_set_bool(App()->GlobalConfig(), "General", + config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutClosingDocks", true); - config_save_safe(App()->GlobalConfig(), "tmp", nullptr); + config_save_safe(App()->GetUserConfig(), "tmp", + nullptr); } }; - bool warned = config_get_bool(App()->GlobalConfig(), "General", + bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutClosingDocks"); if (!OBSBasic::Get()->Closing() && !warned) { QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, diff --git a/UI/window-extra-browsers.cpp b/UI/window-extra-browsers.cpp index e9b7265b62f278..c5bd177e217e14 100644 --- a/UI/window-extra-browsers.cpp +++ b/UI/window-extra-browsers.cpp @@ -473,7 +473,7 @@ void OBSBasic::ClearExtraBrowserDocks() void OBSBasic::LoadExtraBrowserDocks() { const char *jsonStr = config_get_string( - App()->GlobalConfig(), "BasicWindow", "ExtraBrowserDocks"); + App()->GetUserConfig(), "BasicWindow", "ExtraBrowserDocks"); std::string err; Json json = Json::parse(jsonStr, err); @@ -511,7 +511,7 @@ void OBSBasic::SaveExtraBrowserDocks() } std::string output = Json(array).dump(); - config_set_string(App()->GlobalConfig(), "BasicWindow", + config_set_string(App()->GetUserConfig(), "BasicWindow", "ExtraBrowserDocks", output.c_str()); } diff --git a/UI/window-importer.cpp b/UI/window-importer.cpp index 61508a2acd5dc6..e3e50d5598ca5a 100644 --- a/UI/window-importer.cpp +++ b/UI/window-importer.cpp @@ -437,7 +437,7 @@ OBSImporter::OBSImporter(QWidget *parent) ImportersInit(); - bool autoSearchPrompt = config_get_bool(App()->GlobalConfig(), + bool autoSearchPrompt = config_get_bool(App()->GetUserConfig(), "General", "AutoSearchPrompt"); if (!autoSearchPrompt) { @@ -446,18 +446,18 @@ OBSImporter::OBSImporter(QWidget *parent) QTStr("Importer.AutomaticCollectionText")); if (button == QMessageBox::Yes) { - config_set_bool(App()->GlobalConfig(), "General", + config_set_bool(App()->GetUserConfig(), "General", "AutomaticCollectionSearch", true); } else { - config_set_bool(App()->GlobalConfig(), "General", + config_set_bool(App()->GetUserConfig(), "General", "AutomaticCollectionSearch", false); } - config_set_bool(App()->GlobalConfig(), "General", + config_set_bool(App()->GetUserConfig(), "General", "AutoSearchPrompt", true); } - bool autoSearch = config_get_bool(App()->GlobalConfig(), "General", + bool autoSearch = config_get_bool(App()->GetUserConfig(), "General", "AutomaticCollectionSearch"); OBSImporterFiles f; @@ -536,8 +536,11 @@ void OBSImporter::browseImport() bool GetUnusedName(std::string &name) { - if (!SceneCollectionExists(name.c_str())) + OBSBasic *basic = reinterpret_cast(App()->GetMainWindow()); + + if (!basic->GetSceneCollectionByName(name)) { return false; + } std::string newName; int inc = 2; @@ -545,18 +548,21 @@ bool GetUnusedName(std::string &name) newName = name; newName += " "; newName += std::to_string(inc++); - } while (SceneCollectionExists(newName.c_str())); + } while (basic->GetSceneCollectionByName(newName)); name = newName; return true; } +constexpr std::string_view OBSSceneCollectionPath = "obs-studio/basic/scenes/"; + void OBSImporter::importCollections() { setEnabled(false); - char dst[512]; - GetConfigPath(dst, 512, "obs-studio/basic/scenes/"); + const std::filesystem::path sceneCollectionLocation = + App()->userScenesLocation / + std::filesystem::u8path(OBSSceneCollectionPath); for (int i = 0; i < optionsModel->rowCount() - 1; i++) { int selected = optionsModel->index(i, ImporterColumn::Selected) @@ -591,22 +597,35 @@ void OBSImporter::importCollections() out = newOut; } - GetUnusedSceneCollectionFile(name, file); + std::string fileName; + if (!GetFileSafeName(name.c_str(), fileName)) { + blog(LOG_WARNING, + "Failed to create safe file name for '%s'", + fileName.c_str()); + } - std::string save = dst; - save += "/"; - save += file; - save += ".json"; + std::string collectionFile; + collectionFile.reserve( + sceneCollectionLocation.u8string().size() + + fileName.size()); + collectionFile + .append(sceneCollectionLocation.u8string()) + .append(fileName); + + if (!GetClosestUnusedFileName(collectionFile, "json")) { + blog(LOG_WARNING, + "Failed to get closest file name for %s", + fileName.c_str()); + } std::string out_str = json11::Json(out).dump(); - bool success = os_quick_write_utf8_file(save.c_str(), - out_str.c_str(), - out_str.size(), - false); + bool success = os_quick_write_utf8_file( + collectionFile.c_str(), out_str.c_str(), + out_str.size(), false); blog(LOG_INFO, "Import Scene Collection: %s (%s) - %s", - name.c_str(), file.c_str(), + name.c_str(), fileName.c_str(), success ? "SUCCESS" : "FAILURE"); } } diff --git a/UI/window-permissions.cpp b/UI/window-permissions.cpp index eabea1b15a1861..794cf27919c021 100644 --- a/UI/window-permissions.cpp +++ b/UI/window-permissions.cpp @@ -96,7 +96,7 @@ void OBSPermissions::on_accessibilityPermissionButton_clicked() void OBSPermissions::on_continueButton_clicked() { - config_set_int(GetGlobalConfig(), "General", + config_set_int(App()->GetAppConfig(), "General", "MacOSPermissionsDialogLastShown", MACOS_PERMISSIONS_DIALOG_VERSION); close(); diff --git a/UI/window-projector.cpp b/UI/window-projector.cpp index cf0d57b972de3c..94212a9d01a41c 100644 --- a/UI/window-projector.cpp +++ b/UI/window-projector.cpp @@ -28,7 +28,7 @@ OBSProjector::OBSProjector(QWidget *widget, obs_source_t *source_, int monitor, "destroy", OBSSourceDestroyed, this); } - isAlwaysOnTop = config_get_bool(GetGlobalConfig(), "BasicWindow", + isAlwaysOnTop = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ProjectorAlwaysOnTop"); if (isAlwaysOnTop) @@ -144,7 +144,7 @@ void OBSProjector::SetHideCursor() if (savedMonitor == -1) return; - bool hideCursor = config_get_bool(GetGlobalConfig(), "BasicWindow", + bool hideCursor = config_get_bool(App()->GetUserConfig(), "BasicWindow", "HideProjectorCursor"); if (hideCursor && type != ProjectorType::Multiview) @@ -331,20 +331,21 @@ void OBSProjector::EscapeTriggered() void OBSProjector::UpdateMultiview() { MultiviewLayout multiviewLayout = static_cast( - config_get_int(GetGlobalConfig(), "BasicWindow", + config_get_int(App()->GetUserConfig(), "BasicWindow", "MultiviewLayout")); - bool drawLabel = config_get_bool(GetGlobalConfig(), "BasicWindow", + bool drawLabel = config_get_bool(App()->GetUserConfig(), "BasicWindow", "MultiviewDrawNames"); - bool drawSafeArea = config_get_bool(GetGlobalConfig(), "BasicWindow", - "MultiviewDrawAreas"); + bool drawSafeArea = config_get_bool( + App()->GetUserConfig(), "BasicWindow", "MultiviewDrawAreas"); - mouseSwitching = config_get_bool(GetGlobalConfig(), "BasicWindow", + mouseSwitching = config_get_bool(App()->GetUserConfig(), "BasicWindow", "MultiviewMouseSwitch"); - transitionOnDoubleClick = config_get_bool( - GetGlobalConfig(), "BasicWindow", "TransitionOnDoubleClick"); + transitionOnDoubleClick = config_get_bool(App()->GetUserConfig(), + "BasicWindow", + "TransitionOnDoubleClick"); multiview->Update(multiviewLayout, drawLabel, drawSafeArea); } diff --git a/UI/window-youtube-actions.cpp b/UI/window-youtube-actions.cpp index e2d1926dfbaaaa..574881e6efe987 100644 --- a/UI/window-youtube-actions.cpp +++ b/UI/window-youtube-actions.cpp @@ -287,8 +287,8 @@ OBSYoutubeActions::OBSYoutubeActions(QWidget *parent, Auth *auth, workerThread->start(); OBSBasic *main = OBSBasic::Get(); - bool rememberSettings = config_get_bool(main->basicConfig, "YouTube", - "RememberSettings"); + bool rememberSettings = config_get_bool(main->activeConfiguration, + "YouTube", "RememberSettings"); if (rememberSettings) LoadSettings(); @@ -749,83 +749,85 @@ void OBSYoutubeActions::SaveSettings(BroadcastDescription &broadcast) { OBSBasic *main = OBSBasic::Get(); - config_set_string(main->basicConfig, "YouTube", "Title", + config_set_string(main->activeConfiguration, "YouTube", "Title", QT_TO_UTF8(broadcast.title)); - config_set_string(main->basicConfig, "YouTube", "Description", + config_set_string(main->activeConfiguration, "YouTube", "Description", QT_TO_UTF8(broadcast.description)); - config_set_string(main->basicConfig, "YouTube", "Privacy", + config_set_string(main->activeConfiguration, "YouTube", "Privacy", QT_TO_UTF8(broadcast.privacy)); - config_set_string(main->basicConfig, "YouTube", "CategoryID", + config_set_string(main->activeConfiguration, "YouTube", "CategoryID", QT_TO_UTF8(broadcast.category.id)); - config_set_string(main->basicConfig, "YouTube", "Latency", + config_set_string(main->activeConfiguration, "YouTube", "Latency", QT_TO_UTF8(broadcast.latency)); - config_set_bool(main->basicConfig, "YouTube", "MadeForKids", + config_set_bool(main->activeConfiguration, "YouTube", "MadeForKids", broadcast.made_for_kids); - config_set_bool(main->basicConfig, "YouTube", "AutoStart", + config_set_bool(main->activeConfiguration, "YouTube", "AutoStart", broadcast.auto_start); - config_set_bool(main->basicConfig, "YouTube", "AutoStop", + config_set_bool(main->activeConfiguration, "YouTube", "AutoStop", broadcast.auto_start); - config_set_bool(main->basicConfig, "YouTube", "DVR", broadcast.dvr); - config_set_bool(main->basicConfig, "YouTube", "ScheduleForLater", - broadcast.schedul_for_later); - config_set_string(main->basicConfig, "YouTube", "Projection", + config_set_bool(main->activeConfiguration, "YouTube", "DVR", + broadcast.dvr); + config_set_bool(main->activeConfiguration, "YouTube", + "ScheduleForLater", broadcast.schedul_for_later); + config_set_string(main->activeConfiguration, "YouTube", "Projection", QT_TO_UTF8(broadcast.projection)); - config_set_string(main->basicConfig, "YouTube", "ThumbnailFile", + config_set_string(main->activeConfiguration, "YouTube", "ThumbnailFile", QT_TO_UTF8(thumbnailFile)); - config_set_bool(main->basicConfig, "YouTube", "RememberSettings", true); + config_set_bool(main->activeConfiguration, "YouTube", + "RememberSettings", true); } void OBSYoutubeActions::LoadSettings() { OBSBasic *main = OBSBasic::Get(); - const char *title = - config_get_string(main->basicConfig, "YouTube", "Title"); + const char *title = config_get_string(main->activeConfiguration, + "YouTube", "Title"); ui->title->setText(QT_UTF8(title)); - const char *desc = - config_get_string(main->basicConfig, "YouTube", "Description"); + const char *desc = config_get_string(main->activeConfiguration, + "YouTube", "Description"); ui->description->setPlainText(QT_UTF8(desc)); - const char *priv = - config_get_string(main->basicConfig, "YouTube", "Privacy"); + const char *priv = config_get_string(main->activeConfiguration, + "YouTube", "Privacy"); int index = ui->privacyBox->findData(priv); ui->privacyBox->setCurrentIndex(index); - const char *catID = - config_get_string(main->basicConfig, "YouTube", "CategoryID"); + const char *catID = config_get_string(main->activeConfiguration, + "YouTube", "CategoryID"); index = ui->categoryBox->findData(catID); ui->categoryBox->setCurrentIndex(index); - const char *latency = - config_get_string(main->basicConfig, "YouTube", "Latency"); + const char *latency = config_get_string(main->activeConfiguration, + "YouTube", "Latency"); index = ui->latencyBox->findData(latency); ui->latencyBox->setCurrentIndex(index); - bool dvr = config_get_bool(main->basicConfig, "YouTube", "DVR"); + bool dvr = config_get_bool(main->activeConfiguration, "YouTube", "DVR"); ui->checkDVR->setChecked(dvr); - bool forKids = - config_get_bool(main->basicConfig, "YouTube", "MadeForKids"); + bool forKids = config_get_bool(main->activeConfiguration, "YouTube", + "MadeForKids"); if (forKids) ui->yesMakeForKids->setChecked(true); else ui->notMakeForKids->setChecked(true); - bool schedLater = config_get_bool(main->basicConfig, "YouTube", + bool schedLater = config_get_bool(main->activeConfiguration, "YouTube", "ScheduleForLater"); ui->checkScheduledLater->setChecked(schedLater); - bool autoStart = - config_get_bool(main->basicConfig, "YouTube", "AutoStart"); + bool autoStart = config_get_bool(main->activeConfiguration, "YouTube", + "AutoStart"); ui->checkAutoStart->setChecked(autoStart); - bool autoStop = - config_get_bool(main->basicConfig, "YouTube", "AutoStop"); + bool autoStop = config_get_bool(main->activeConfiguration, "YouTube", + "AutoStop"); ui->checkAutoStop->setChecked(autoStop); - const char *projection = - config_get_string(main->basicConfig, "YouTube", "Projection"); + const char *projection = config_get_string(main->activeConfiguration, + "YouTube", "Projection"); if (projection && *projection) { if (strcmp(projection, "360") == 0) ui->check360Video->setChecked(true); @@ -833,8 +835,8 @@ void OBSYoutubeActions::LoadSettings() ui->check360Video->setChecked(false); } - const char *thumbFile = config_get_string(main->basicConfig, "YouTube", - "ThumbnailFile"); + const char *thumbFile = config_get_string(main->activeConfiguration, + "YouTube", "ThumbnailFile"); if (thumbFile && *thumbFile) { QFileInfo tFile(thumbFile); // Re-check validity before setting path again