diff --git a/obs/CMakeLists.txt b/obs/CMakeLists.txt index 97a99fc3a..973d49cff 100644 --- a/obs/CMakeLists.txt +++ b/obs/CMakeLists.txt @@ -101,6 +101,7 @@ set(obs_SOURCES window-basic-main-outputs.cpp window-basic-source-select.cpp window-basic-main-scene-collections.cpp + window-basic-main-profiles.cpp window-license-agreement.cpp window-basic-status-bar.cpp window-basic-adv-audio.cpp diff --git a/obs/data/locale/en-US.ini b/obs/data/locale/en-US.ini index 07adf5708..3b04fc320 100644 --- a/obs/data/locale/en-US.ini +++ b/obs/data/locale/en-US.ini @@ -43,6 +43,7 @@ New="New" Duplicate="Duplicate" # title bar strings +TitleBar.Profile="Profile" TitleBar.Scenes="Scenes" # "name already exists" dialog box @@ -136,6 +137,13 @@ Basic.Main.AddSceneCollection.Text="Please enter the name of the scene collectio # rename scene collection dialog Basic.Main.RenameSceneCollection.Title="Rename Scene Collection" +# add profile dialog +AddProfile.Title="Add Profile" +AddProfile.Text="Please enter the name of the profile" + +# rename profile dialog +RenameProfile.Title="Rename Profile" + # preview window disabled Basic.Main.PreviewDisabled="Preview is currently disabled" @@ -251,6 +259,7 @@ Basic.MainMenu.Edit.AdvAudio="&Advanced Audio Properties" # basic mode profile/scene collection menus Basic.MainMenu.SceneCollection="&Scene Collection" +Basic.MainMenu.Profile="&Profile" # basic mode help menu Basic.MainMenu.Help="&Help" diff --git a/obs/forms/OBSBasic.ui b/obs/forms/OBSBasic.ui index bd95e46f5..818121886 100644 --- a/obs/forms/OBSBasic.ui +++ b/obs/forms/OBSBasic.ui @@ -628,6 +628,16 @@ + + + Basic.MainMenu.Profile + + + + + + + Basic.MainMenu.SceneCollection @@ -640,6 +650,7 @@ + @@ -963,6 +974,26 @@ Remove + + + New + + + + + Duplicate + + + + + Rename + + + + + Remove + + diff --git a/obs/obs-app.cpp b/obs/obs-app.cpp index a25ad5821..42ec722c8 100644 --- a/obs/obs-app.cpp +++ b/obs/obs-app.cpp @@ -282,6 +282,11 @@ static bool MakeUserProfileDirs() { char path[512]; + if (GetConfigPath(path, sizeof(path), "obs-studio/basic/profiles") <= 0) + return false; + if (!do_mkdir(path)) + return false; + if (GetConfigPath(path, sizeof(path), "obs-studio/basic/scenes") <= 0) return false; if (!do_mkdir(path)) @@ -418,6 +423,61 @@ OBSApp::OBSApp(int &argc, char **argv) : QApplication(argc, argv) {} +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)) + 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; + + if (os_mkdir(new_path) == MKDIR_ERROR) + return; + + strcat(new_path, "/"); + strcat(new_path, Str("Untitled")); + if (os_mkdir(new_path) == MKDIR_ERROR) + return; + + strcat(path, "/*.*"); + if (os_glob(path, 0, &glob) != 0) + return; + + strcpy(path, new_path); + + for (size_t i = 0; i < glob->gl_pathc; i++) { + struct os_globent ent = glob->gl_pathv[i]; + char *file; + + if (ent.directory) + continue; + + file = strrchr(ent.path, '/'); + if (!file++) + continue; + + if (astrcmpi(file, "scenes.json") == 0) + continue; + + strcpy(new_path, path); + strcat(new_path, "/"); + strcat(new_path, file); + os_rename(ent.path, new_path); + } + + os_globfree(glob); +} + static void move_basic_to_scene_collections(void) { char path[512]; @@ -456,11 +516,17 @@ void OBSApp::AppInit() throw "Failed to load locale"; if (!InitTheme()) throw "Failed to load theme"; + + config_set_default_string(globalConfig, "Basic", "Profile", + Str("Untitled")); + config_set_default_string(globalConfig, "Basic", "ProfileDir", + Str("Untitled")); config_set_default_string(globalConfig, "Basic", "SceneCollection", Str("Untitled")); config_set_default_string(globalConfig, "Basic", "SceneCollectionFile", Str("Untitled")); + move_basic_to_profiles(); move_basic_to_scene_collections(); if (!MakeUserProfileDirs()) diff --git a/obs/obs-app.hpp b/obs/obs-app.hpp index 78055cff2..4b0cee3c0 100644 --- a/obs/obs-app.hpp +++ b/obs/obs-app.hpp @@ -115,3 +115,10 @@ inline const char *Str(const char *lookup) {return App()->GetString(lookup);} bool GetFileSafeName(const char *name, std::string &file); bool GetClosestUnusedFileName(std::string &path, const char *extension); + +static inline int GetProfilePath(char *path, size_t size, const char *file) +{ + OBSMainWindow *window = reinterpret_cast( + App()->GetMainWindow()); + return window->GetProfilePath(path, size, file); +} diff --git a/obs/window-basic-main-outputs.cpp b/obs/window-basic-main-outputs.cpp index c2c6bf46c..a8e849020 100644 --- a/obs/window-basic-main-outputs.cpp +++ b/obs/window-basic-main-outputs.cpp @@ -315,7 +315,7 @@ static OBSData GetDataFromJsonFile(const char *jsonFile) { char fullPath[512]; - int ret = GetConfigPath(fullPath, sizeof(fullPath), jsonFile); + int ret = GetProfilePath(fullPath, sizeof(fullPath), jsonFile); if (ret > 0) { BPtr jsonData = os_quick_read_utf8_file(fullPath); if (!!jsonData) { @@ -341,10 +341,8 @@ AdvancedOutput::AdvancedOutput(OBSBasic *main_) : BasicOutputHandler(main_) ffmpegRecording = astrcmpi(recType, "FFmpeg") == 0; useStreamEncoder = astrcmpi(recordEncoder, "none") == 0; - OBSData streamEncSettings = GetDataFromJsonFile( - "obs-studio/basic/streamEncoder.json"); - OBSData recordEncSettings = GetDataFromJsonFile( - "obs-studio/basic/recordEncoder.json"); + OBSData streamEncSettings = GetDataFromJsonFile("streamEncoder.json"); + OBSData recordEncSettings = GetDataFromJsonFile("recordEncoder.json"); streamOutput = obs_output_create("rtmp_output", "adv_stream", nullptr, nullptr); @@ -411,8 +409,7 @@ void AdvancedOutput::UpdateStreamSettings() bool applyServiceSettings = config_get_bool(main->Config(), "AdvOut", "ApplyServiceSettings"); - OBSData settings = GetDataFromJsonFile( - "obs-studio/basic/streamEncoder.json"); + OBSData settings = GetDataFromJsonFile("streamEncoder.json"); if (applyServiceSettings) obs_service_apply_encoder_settings(main->GetService(), @@ -430,8 +427,7 @@ void AdvancedOutput::UpdateStreamSettings() inline void AdvancedOutput::UpdateRecordingSettings() { - OBSData settings = GetDataFromJsonFile( - "obs-studio/basic/recordEncoder.json"); + OBSData settings = GetDataFromJsonFile("recordEncoder.json"); obs_encoder_update(h264Recording, settings); } diff --git a/obs/window-basic-main-profiles.cpp b/obs/window-basic-main-profiles.cpp new file mode 100644 index 000000000..2f159609e --- /dev/null +++ b/obs/window-basic-main-profiles.cpp @@ -0,0 +1,483 @@ +/****************************************************************************** + Copyright (C) 2015 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include +#include +#include +#include +#include +#include "window-basic-main.hpp" +#include "window-namedialog.hpp" +#include "qt-wrappers.hpp" + +template static void EnumProfiles(Func &&cb) +{ + char path[512]; + os_glob_t *glob; + + int ret = GetConfigPath(path, sizeof(path), + "obs-studio/basic/profiles/*"); + if (ret <= 0) { + blog(LOG_WARNING, "Failed to get profiles config path"); + return; + } + + if (os_glob(path, 0, &glob) != 0) { + blog(LOG_WARNING, "Failed to glob profiles"); + return; + } + + for (size_t i = 0; i < glob->gl_pathc; i++) { + const char *filePath = glob->gl_pathv[i].path; + const char *dirName = strrchr(filePath, '/') + 1; + + if (!glob->gl_pathv[i].directory) + continue; + + if (strcmp(dirName, ".") == 0 || + strcmp(dirName, "..") == 0) + continue; + + std::string file = filePath; + file += "/basic.ini"; + + ConfigFile config; + int ret = config.Open(file.c_str(), CONFIG_OPEN_EXISTING); + if (ret != CONFIG_SUCCESS) + continue; + + const char *name = config_get_string(config, "General", "Name"); + if (!name) + name = strrchr(filePath, '/') + 1; + + if (!cb(name, filePath)) + break; + } + + os_globfree(glob); +} + +static bool ProfileExists(const char *findName) +{ + bool found = false; + auto func = [&](const char *name, const char*) + { + if (strcmp(name, findName) == 0) { + found = true; + return false; + } + return true; + }; + + EnumProfiles(func); + return found; +} + +static bool GetProfileName(QWidget *parent, std::string &name, + std::string &file, const char *title, const char *text, + const char *oldName = nullptr) +{ + char path[512]; + int ret; + + for (;;) { + bool success = NameDialog::AskForName(parent, title, text, + name, QT_UTF8(oldName)); + if (!success) { + return false; + } + if (name.empty()) { + QMessageBox::information(parent, + QTStr("NoNameEntered.Title"), + QTStr("NoNameEntered.Text")); + continue; + } + if (ProfileExists(name.c_str())) { + QMessageBox::information(parent, + QTStr("NameExists.Title"), + QTStr("NameExists.Text")); + continue; + } + break; + } + + 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/profiles/"); + if (ret <= 0) { + blog(LOG_WARNING, "Failed to get profiles config path"); + return false; + } + + file.insert(0, path); + + if (!GetClosestUnusedFileName(file, nullptr)) { + blog(LOG_WARNING, "Failed to get closest file name for %s", + file.c_str()); + return false; + } + + file.erase(0, ret); + return true; +} + +static bool CopyProfile(const char *fromPartial, const char *to) +{ + os_glob_t *glob; + char path[512]; + int ret; + + ret = GetConfigPath(path, sizeof(path), "obs-studio/basic/profiles/"); + if (ret <= 0) { + blog(LOG_WARNING, "Failed to get profiles config path"); + return false; + } + + strcat(path, fromPartial); + strcat(path, "/*"); + + if (os_glob(path, 0, &glob) != 0) { + blog(LOG_WARNING, "Failed to glob profile '%s'", fromPartial); + 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); + } + } + } + + os_globfree(glob); + + return true; +} + +bool OBSBasic::AddProfile(bool create_new, const char *title, const char *text, + const char *init_text) +{ + std::string newName; + std::string newDir; + ConfigFile config; + + if (!GetProfileName(this, newName, newDir, title, text, init_text)) + return false; + + std::string curDir = config_get_string(App()->GlobalConfig(), + "Basic", "ProfileDir"); + + char newPath[512]; + int ret = GetConfigPath(newPath, 512, "obs-studio/basic/profiles/"); + if (ret <= 0) { + blog(LOG_WARNING, "Failed to get profiles config path"); + return false; + } + + strcat(newPath, newDir.c_str()); + + if (os_mkdir(newPath) < 0) { + blog(LOG_WARNING, "Failed to create profile directory '%s'", + newDir.c_str()); + return false; + } + + if (!create_new) + CopyProfile(curDir.c_str(), newPath); + + strcat(newPath, "/basic.ini"); + + if (config.Open(newPath, CONFIG_OPEN_ALWAYS) != 0) { + blog(LOG_ERROR, "Failed to open new config file '%s'", + newDir.c_str()); + return false; + } + + config_set_string(App()->GlobalConfig(), "Basic", "Profile", + newName.c_str()); + config_set_string(App()->GlobalConfig(), "Basic", "ProfileDir", + newDir.c_str()); + + config_set_string(config, "General", "Name", newName.c_str()); + config.Save(); + config.Swap(basicConfig); + InitBasicConfigDefaults(); + RefreshProfiles(); + + if (create_new) + ResetProfileData(); + + blog(LOG_INFO, "------------------------------------------------"); + blog(LOG_INFO, "Created profile '%s' (%s, %s)", newName.c_str(), + create_new ? "clean" : "duplicate", newDir.c_str()); + + config_save(App()->GlobalConfig()); + UpdateTitleBar(); + return true; +} + +void OBSBasic::DeleteProfile(const char *profileName, const char *profileDir) +{ + char profilePath[512]; + char basePath[512]; + + int ret = GetConfigPath(basePath, 512, "obs-studio/basic/profiles"); + if (ret <= 0) { + blog(LOG_WARNING, "Failed to get profiles config path"); + return; + } + + ret = snprintf(profilePath, 512, "%s/%s/*", basePath, profileDir); + if (ret <= 0) { + blog(LOG_WARNING, "Failed to get path for profile dir '%s'", + profileDir); + return; + } + + os_glob_t *glob; + if (os_glob(profilePath, 0, &glob) != 0) { + blog(LOG_WARNING, "Failed to glob profile dir '%s'", + profileDir); + return; + } + + 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; + + os_unlink(filePath); + } + + os_globfree(glob); + + ret = snprintf(profilePath, 512, "%s/%s", basePath, profileDir); + if (ret <= 0) { + blog(LOG_WARNING, "Failed to get path for profile dir '%s'", + profileDir); + return; + } + + os_rmdir(profilePath); + + blog(LOG_INFO, "------------------------------------------------"); + blog(LOG_INFO, "Removed profile '%s' (%s)", + profileName, profileDir); +} + +void OBSBasic::RefreshProfiles() +{ + QList menuActions = ui->profileMenu->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]; + } + + 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); + + action->setChecked(strcmp(name, curName) == 0); + + ui->profileMenu->addAction(action); + count++; + return true; + }; + + EnumProfiles(addProfile); + + ui->actionRemoveProfile->setEnabled(count > 1); +} + +void OBSBasic::ResetProfileData() +{ + ResetVideo(); + service = nullptr; + InitService(); + ResetOutputs(); + ClearHotkeys(); + CreateHotkeys(); +} + +void OBSBasic::on_actionNewProfile_triggered() +{ + AddProfile(true, Str("AddProfile.Title"), Str("AddProfile.Text")); +} + +void OBSBasic::on_actionDupProfile_triggered() +{ + AddProfile(false, Str("AddProfile.Title"), Str("AddProfile.Text")); +} + +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"); + + /* 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()); + if (success) { + DeleteProfile(curName.c_str(), curDir.c_str()); + RefreshProfiles(); + } +} + +void OBSBasic::on_actionRemoveProfile_triggered() +{ + std::string newName; + std::string newPath; + ConfigFile config; + + std::string oldDir = config_get_string(App()->GlobalConfig(), + "Basic", "ProfileDir"); + std::string oldName = config_get_string(App()->GlobalConfig(), + "Basic", "Profile"); + + auto cb = [&](const char *name, const char *filePath) + { + if (strcmp(oldName.c_str(), name) != 0) { + newName = name; + newPath = filePath; + return false; + } + + return true; + }; + + EnumProfiles(cb); + + /* this should never be true due to menu item being grayed out */ + if (newPath.empty()) + return; + + QString text = QTStr("ConfirmRemove.Text"); + text.replace("$1", QT_UTF8(oldName.c_str())); + + QMessageBox::StandardButton button = QMessageBox::question(this, + QTStr("ConfirmRemove.Title"), text); + if (button == QMessageBox::No) + return; + + size_t newPath_len = newPath.size(); + newPath += "/basic.ini"; + + if (config.Open(newPath.c_str(), CONFIG_OPEN_ALWAYS) != 0) { + blog(LOG_ERROR, "ChangeProfile: Failed to load file '%s'", + newPath.c_str()); + return; + } + + newPath.resize(newPath_len); + + const char *newDir = strrchr(newPath.c_str(), '/') + 1; + + config_set_string(App()->GlobalConfig(), "Basic", "Profile", + newName.c_str()); + config_set_string(App()->GlobalConfig(), "Basic", "ProfileDir", + newDir); + + config.Swap(basicConfig); + InitBasicConfigDefaults(); + ResetProfileData(); + DeleteProfile(oldName.c_str(), oldDir.c_str()); + RefreshProfiles(); + config_save(App()->GlobalConfig()); + + blog(LOG_INFO, "------------------------------------------------"); + blog(LOG_INFO, "Switched to profile '%s' (%s)", + newName.c_str(), newDir); + + UpdateTitleBar(); +} + +void OBSBasic::ChangeProfile() +{ + QAction *action = reinterpret_cast(sender()); + ConfigFile config; + std::string path; + + if (!action) + return; + + path = QT_TO_UTF8(action->property("file_name").value()); + if (path.empty()) + return; + + const char *oldName = config_get_string(App()->GlobalConfig(), + "Basic", "Profile"); + if (action->text().compare(QT_UTF8(oldName)) == 0) { + action->setChecked(true); + return; + } + + size_t path_len = path.size(); + path += "/basic.ini"; + + if (config.Open(path.c_str(), CONFIG_OPEN_ALWAYS) != 0) { + blog(LOG_ERROR, "ChangeProfile: Failed to load file '%s'", + path.c_str()); + return; + } + + path.resize(path_len); + + const char *newName = config_get_string(config, "General", "Name"); + const char *newDir = strrchr(path.c_str(), '/') + 1; + + config_set_string(App()->GlobalConfig(), "Basic", "Profile", newName); + config_set_string(App()->GlobalConfig(), "Basic", "ProfileDir", + newDir); + + blog(LOG_INFO, "------------------------------------------------"); + blog(LOG_INFO, "Switched to profile '%s' (%s)", + newName, newDir); + + config.Swap(basicConfig); + InitBasicConfigDefaults(); + ResetProfileData(); + RefreshProfiles(); + config_save(App()->GlobalConfig()); + UpdateTitleBar(); +} diff --git a/obs/window-basic-main.cpp b/obs/window-basic-main.cpp index 60bc8b8bf..842dc9842 100644 --- a/obs/window-basic-main.cpp +++ b/obs/window-basic-main.cpp @@ -119,8 +119,8 @@ OBSBasic::OBSBasic(QWidget *parent) } char styleSheetPath[512]; - int ret = GetConfigPath(styleSheetPath, sizeof(styleSheetPath), - "obs-studio/basic/stylesheet.qss"); + int ret = GetProfilePath(styleSheetPath, sizeof(styleSheetPath), + "stylesheet.qss"); if (ret > 0) { if (QFile::exists(styleSheetPath)) { QString path = QString("file:///") + @@ -476,7 +476,7 @@ void OBSBasic::Load(const char *file) disableSaving--; } -#define SERVICE_PATH "obs-studio/basic/service.json" +#define SERVICE_PATH "service.json" void OBSBasic::SaveService() { @@ -484,7 +484,7 @@ void OBSBasic::SaveService() return; char serviceJsonPath[512]; - int ret = GetConfigPath(serviceJsonPath, sizeof(serviceJsonPath), + int ret = GetProfilePath(serviceJsonPath, sizeof(serviceJsonPath), SERVICE_PATH); if (ret <= 0) return; @@ -508,7 +508,7 @@ bool OBSBasic::LoadService() const char *type; char serviceJsonPath[512]; - int ret = GetConfigPath(serviceJsonPath, sizeof(serviceJsonPath), + int ret = GetProfilePath(serviceJsonPath, sizeof(serviceJsonPath), SERVICE_PATH); if (ret <= 0) return false; @@ -690,8 +690,19 @@ bool OBSBasic::InitBasicConfigDefaults() bool OBSBasic::InitBasicConfig() { char configPath[512]; - int ret = GetConfigPath(configPath, sizeof(configPath), - "obs-studio/basic/basic.ini"); + + 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; + } + + ret = GetProfilePath(configPath, sizeof(configPath), "basic.ini"); if (ret <= 0) { OBSErrorBox(nullptr, "Failed to get base.ini path"); return false; @@ -703,6 +714,14 @@ bool OBSBasic::InitBasicConfig() return false; } + if (config_get_string(basicConfig, "General", "Name") == nullptr) { + const char *curName = config_get_string(App()->GlobalConfig(), + "Basic", "Profile"); + + config_set_string(basicConfig, "General", "Name", curName); + basicConfig.Save(); + } + return InitBasicConfigDefaults(); } @@ -844,6 +863,7 @@ void OBSBasic::OBSInit() #endif RefreshSceneCollections(); + RefreshProfiles(); disableSaving--; } @@ -2887,6 +2907,7 @@ void OBSBasic::StopStreaming() outputHandler->StopStreaming(); if (!outputHandler->Active()) { + ui->profileMenu->setEnabled(true); blog(LOG_INFO, STREAMING_STOP); } } @@ -2896,6 +2917,7 @@ void OBSBasic::StreamingStart() ui->streamButton->setText(QTStr("Basic.Main.StopStreaming")); ui->streamButton->setEnabled(true); ui->statusbar->StreamStarted(outputHandler->streamOutput); + ui->profileMenu->setEnabled(false); } void OBSBasic::StreamingStop(int code) @@ -2932,6 +2954,7 @@ void OBSBasic::StreamingStop(int code) ui->streamButton->setEnabled(true); if (!outputHandler->Active()) { + ui->profileMenu->setEnabled(true); blog(LOG_INFO, STREAMING_STOP); } @@ -2960,6 +2983,7 @@ void OBSBasic::StopRecording() outputHandler->StopRecording(); if (!outputHandler->Active()) { + ui->profileMenu->setEnabled(true); blog(LOG_INFO, RECORDING_STOP); } } @@ -2968,6 +2992,7 @@ void OBSBasic::RecordingStart() { ui->statusbar->RecordingStarted(outputHandler->fileOutput); ui->recordButton->setText(QTStr("Basic.Main.StopRecording")); + ui->profileMenu->setEnabled(false); } void OBSBasic::RecordingStop(int code) @@ -2981,6 +3006,7 @@ void OBSBasic::RecordingStop(int code) QTStr("Output.RecordFail.Unsupported")); if (!outputHandler->Active()) { + ui->profileMenu->setEnabled(true); blog(LOG_INFO, RECORDING_STOP); } } @@ -3434,12 +3460,39 @@ void OBSBasic::UpdateTitleBar() { stringstream name; + const char *profile = config_get_string(App()->GlobalConfig(), + "Basic", "Profile"); const char *sceneCollection = config_get_string(App()->GlobalConfig(), "Basic", "SceneCollection"); name << "OBS " << App()->GetVersionString(); + name << " - " << Str("TitleBar.Profile") << ": " << profile; name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection; blog(LOG_INFO, "%s", name.str().c_str()); setWindowTitle(QT_UTF8(name.str().c_str())); } + +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"); + int ret; + + if (!profile) + return -1; + if (!path) + return -1; + if (!file) + file = ""; + + ret = GetConfigPath(profiles_path, 512, "obs-studio/basic/profiles"); + if (ret <= 0) + return ret; + + if (!*file) + return snprintf(path, size, "%s/%s", profiles_path, profile); + + return snprintf(path, size, "%s/%s/%s", profiles_path, profile, file); +} diff --git a/obs/window-basic-main.hpp b/obs/window-basic-main.hpp index bacc3eab0..4730e3450 100644 --- a/obs/window-basic-main.hpp +++ b/obs/window-basic-main.hpp @@ -174,6 +174,14 @@ private: void RefreshSceneCollections(); void ChangeSceneCollection(); + void LoadProfile(); + void ResetProfileData(); + bool AddProfile(bool create_new, const char *title, const char *text, + const char *init_text = nullptr); + void DeleteProfile(const char *profile_name, const char *profile_dir); + void RefreshProfiles(); + void ChangeProfile(); + obs_hotkey_pair_id streamingHotkeys, recordingHotkeys; public slots: @@ -343,6 +351,11 @@ private slots: void on_actionRenameSceneCollection_triggered(); void on_actionRemoveSceneCollection_triggered(); + void on_actionNewProfile_triggered(); + void on_actionDupProfile_triggered(); + void on_actionRenameProfile_triggered(); + void on_actionRemoveProfile_triggered(); + void logUploadFinished(const QString &text, const QString &error); void updateFileFinished(const QString &text, const QString &error); @@ -382,6 +395,9 @@ public: virtual config_t *Config() const override; + virtual int GetProfilePath(char *path, size_t size, const char *file) + const; + private: std::unique_ptr ui; }; diff --git a/obs/window-basic-settings.cpp b/obs/window-basic-settings.cpp index 970a5774c..eccb30bb5 100644 --- a/obs/window-basic-settings.cpp +++ b/obs/window-basic-settings.cpp @@ -1006,7 +1006,7 @@ OBSPropertiesView *OBSBasicSettings::CreateEncoderPropertyView( OBSPropertiesView *view; char encoderJsonPath[512]; - int ret = GetConfigPath(encoderJsonPath, sizeof(encoderJsonPath), + int ret = GetProfilePath(encoderJsonPath, sizeof(encoderJsonPath), path); if (ret > 0) { BPtr jsonData = os_quick_read_utf8_file(encoderJsonPath); @@ -1035,7 +1035,7 @@ void OBSBasicSettings::LoadAdvOutputStreamingEncoderProperties() delete streamEncoderProps; streamEncoderProps = CreateEncoderPropertyView(encoder, - "obs-studio/basic/streamEncoder.json"); + "streamEncoder.json"); ui->advOutputStreamTab->layout()->addWidget(streamEncoderProps); SetComboByValue(ui->advOutEncoder, encoder); @@ -1080,7 +1080,7 @@ void OBSBasicSettings::LoadAdvOutputRecordingEncoderProperties() if (astrcmpi(encoder, "none") != 0) { recordEncoderProps = CreateEncoderPropertyView(encoder, - "obs-studio/basic/recordEncoder.json"); + "recordEncoder.json"); ui->advOutRecStandard->layout()->addWidget(recordEncoderProps); } @@ -1914,7 +1914,7 @@ static void WriteJsonData(OBSPropertiesView *view, const char *path) if (!view || !WidgetChanged(view)) return; - int ret = GetConfigPath(full_path, sizeof(full_path), path); + int ret = GetProfilePath(full_path, sizeof(full_path), path); if (ret > 0) { obs_data_t *settings = view->GetSettings(); if (settings) { @@ -2042,10 +2042,8 @@ void OBSBasicSettings::SaveOutputSettings() SaveEdit(ui->advOutTrack3Name, "AdvOut", "Track3Name"); SaveEdit(ui->advOutTrack4Name, "AdvOut", "Track4Name"); - WriteJsonData(streamEncoderProps, - "obs-studio/basic/streamEncoder.json"); - WriteJsonData(recordEncoderProps, - "obs-studio/basic/recordEncoder.json"); + WriteJsonData(streamEncoderProps, "streamEncoder.json"); + WriteJsonData(recordEncoderProps, "recordEncoder.json"); main->ResetOutputs(); } @@ -2315,7 +2313,7 @@ void OBSBasicSettings::on_advOutEncoder_currentIndexChanged(int idx) delete streamEncoderProps; streamEncoderProps = CreateEncoderPropertyView(QT_TO_UTF8(encoder), - "obs-studio/basic/streamEncoder.json", true); + "streamEncoder.json", true); ui->advOutputStreamTab->layout()->addWidget(streamEncoderProps); UNUSED_PARAMETER(idx); @@ -2334,7 +2332,7 @@ void OBSBasicSettings::on_advOutRecEncoder_currentIndexChanged(int idx) recordEncoderProps = CreateEncoderPropertyView( QT_TO_UTF8(encoder), - "obs-studio/basic/recordEncoder.json", true); + "recordEncoder.json", true); ui->advOutRecStandard->layout()->addWidget(recordEncoderProps); } } diff --git a/obs/window-main.hpp b/obs/window-main.hpp index c98be146e..ec24988ab 100644 --- a/obs/window-main.hpp +++ b/obs/window-main.hpp @@ -12,4 +12,7 @@ public: virtual config_t *Config() const=0; virtual void OBSInit()=0; + + virtual int GetProfilePath(char *path, size_t size, const char *file) + const=0; };