UI: Rewrite profile system to enable user-provided storage location

This change enables loading profiles from locations different than
OBS' own configuration directory.

It also rewrites profile management in the app to work off an in-memory
collection of profiles found on disk and does not require iterating
over directory contents for most profile interactions by the app.
This commit is contained in:
PatTheMav 2024-09-03 16:29:30 +02:00 committed by Ryan Foster
parent 2635cf3a2a
commit 607d37b423
9 changed files with 1772 additions and 1601 deletions

View File

@ -16,7 +16,6 @@ template<typename T> static T GetOBSRef(QListWidgetItem *item)
return item->data(static_cast<int>(QtDataRole::OBSRef)).value<T>();
}
void EnumProfiles(function<bool(const char *, const char *)> &&cb);
void EnumSceneCollections(function<bool(const char *, const char *)> &&cb);
extern volatile bool streaming_active;
@ -218,29 +217,24 @@ struct OBSStudioAPI : obs_frontend_callbacks {
void
obs_frontend_get_profiles(std::vector<std::string> &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,353 +504,323 @@ struct OBSStudioAPI : obs_frontend_callbacks {
config_t *obs_frontend_get_profile_config(void) override
{
return main->basicConfig;
config_t *obs_frontend_get_global_config(void) override
{
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,
const char *geometry,
const char *name) override
{
SavedProjectorInfo proj = {
ProjectorType::Preview,
monitor,
geometry ? geometry : "",
name ? name : "",
};
if (type) {
if (astrcmpi(type, "Source") == 0)
proj.type = ProjectorType::Source;
else if (astrcmpi(type, "Scene") == 0)
proj.type = ProjectorType::Scene;
else if (astrcmpi(type, "StudioProgram") == 0)
proj.type =
ProjectorType::StudioProgram;
else if (astrcmpi(type, "Multiview") == 0)
proj.type = ProjectorType::Multiview;
}
QMetaObject::invokeMethod(
main, "OpenSavedProjector", WaitConnection(),
Q_ARG(SavedProjectorInfo *, &proj));
}
void obs_frontend_save(void) override
{
main->SaveProject();
}
void obs_frontend_defer_save_begin(void) override
{
QMetaObject::invokeMethod(main, "DeferSaveBegin");
}
void obs_frontend_defer_save_end(void) override
{
QMetaObject::invokeMethod(main, "DeferSaveEnd");
}
void obs_frontend_add_save_callback(
obs_frontend_save_cb callback, void *private_data)
override
{
size_t idx = GetCallbackIdx(saveCallbacks, callback,
private_data);
if (idx == (size_t)-1)
saveCallbacks.emplace_back(callback,
private_data);
}
void obs_frontend_remove_save_callback(
obs_frontend_save_cb callback, void *private_data)
override
{
size_t idx = GetCallbackIdx(saveCallbacks, callback,
private_data);
if (idx == (size_t)-1)
return;
saveCallbacks.erase(saveCallbacks.begin() + idx);
}
void obs_frontend_add_preload_callback(
obs_frontend_save_cb callback, void *private_data)
override
{
size_t idx = GetCallbackIdx(preloadCallbacks, callback,
private_data);
if (idx == (size_t)-1)
preloadCallbacks.emplace_back(callback,
private_data);
}
void obs_frontend_remove_preload_callback(
obs_frontend_save_cb callback, void *private_data)
override
{
size_t idx = GetCallbackIdx(preloadCallbacks, callback,
private_data);
if (idx == (size_t)-1)
return;
preloadCallbacks.erase(preloadCallbacks.begin() + idx);
}
void obs_frontend_push_ui_translation(
obs_frontend_translate_ui_cb translate) override
{
App()->PushUITranslation(translate);
}
void obs_frontend_pop_ui_translation(void) override
{
App()->PopUITranslation();
}
void obs_frontend_set_streaming_service(obs_service_t * service)
override
{
main->SetService(service);
}
obs_service_t *obs_frontend_get_streaming_service(void) override
{
return main->GetService();
}
void obs_frontend_save_streaming_service(void) override
{
main->SaveService();
}
bool obs_frontend_preview_program_mode_active(void) override
{
return main->IsPreviewProgramMode();
}
void obs_frontend_set_preview_program_mode(bool enable) override
{
main->SetPreviewProgramMode(enable);
}
void obs_frontend_preview_program_trigger_transition(void)
override
{
QMetaObject::invokeMethod(main, "TransitionClicked");
}
bool obs_frontend_preview_enabled(void) override
{
return main->previewEnabled;
}
void obs_frontend_set_preview_enabled(bool enable) override
{
if (main->previewEnabled != enable)
main->EnablePreviewDisplay(enable);
}
obs_source_t *obs_frontend_get_current_preview_scene(void)
override
{
if (main->IsPreviewProgramMode()) {
OBSSource source =
main->GetCurrentSceneSource();
return obs_source_get_ref(source);
}
return nullptr;
}
void obs_frontend_set_current_preview_scene(obs_source_t *
scene) override
{
if (main->IsPreviewProgramMode()) {
QMetaObject::invokeMethod(
main, "SetCurrentScene",
Q_ARG(OBSSource, OBSSource(scene)),
Q_ARG(bool, false));
}
}
void obs_frontend_take_screenshot(void) override
{
QMetaObject::invokeMethod(main, "Screenshot");
}
void obs_frontend_take_source_screenshot(obs_source_t * source)
override
{
QMetaObject::invokeMethod(main, "Screenshot",
Q_ARG(OBSSource,
OBSSource(source)));
}
obs_output_t *obs_frontend_get_virtualcam_output(void) override
{
OBSOutput output =
main->outputHandler->virtualCam.Get();
return obs_output_get_ref(output);
}
void obs_frontend_start_virtualcam(void) override
{
QMetaObject::invokeMethod(main, "StartVirtualCam");
}
void obs_frontend_stop_virtualcam(void) override
{
QMetaObject::invokeMethod(main, "StopVirtualCam");
}
bool obs_frontend_virtualcam_active(void) override
{
return os_atomic_load_bool(&virtualcam_active);
}
void obs_frontend_reset_video(void) override
{
main->ResetVideo();
}
void obs_frontend_open_source_properties(obs_source_t * source)
override
{
QMetaObject::invokeMethod(main, "OpenProperties",
Q_ARG(OBSSource,
OBSSource(source)));
}
void obs_frontend_open_source_filters(obs_source_t * source)
override
{
QMetaObject::invokeMethod(main, "OpenFilters",
Q_ARG(OBSSource,
OBSSource(source)));
}
void obs_frontend_open_source_interaction(obs_source_t * source)
override
{
QMetaObject::invokeMethod(main, "OpenInteraction",
Q_ARG(OBSSource,
OBSSource(source)));
}
void obs_frontend_open_sceneitem_edit_transform(
obs_sceneitem_t * item) override
{
QMetaObject::invokeMethod(main, "OpenEditTransform",
Q_ARG(OBSSceneItem,
OBSSceneItem(item)));
}
char *obs_frontend_get_current_record_output_path(void) override
{
const char *recordOutputPath =
main->GetCurrentOutputPath();
return bstrdup(recordOutputPath);
}
const char *obs_frontend_get_locale_string(const char *string)
override
{
return Str(string);
}
bool obs_frontend_is_theme_dark(void) override
{
return App()->IsThemeDark();
}
char *obs_frontend_get_last_recording(void) override
{
return bstrdup(
main->outputHandler->lastRecordingPath.c_str());
}
char *obs_frontend_get_last_screenshot(void) override
{
return bstrdup(main->lastScreenshot.c_str());
}
char *obs_frontend_get_last_replay(void) override
{
return bstrdup(main->lastReplay.c_str());
}
void obs_frontend_add_undo_redo_action(
const char *name, const undo_redo_cb undo,
const undo_redo_cb redo, const char *undo_data,
const char *redo_data, bool repeatable) override
{
main->undo_s.add_action(
name,
[undo](const std::string &data) {
undo(data.c_str());
},
[redo](const std::string &data) {
redo(data.c_str());
},
undo_data, redo_data, repeatable);
}
void on_load(obs_data_t * settings) override
{
for (size_t i = saveCallbacks.size(); i > 0; i--) {
auto cb = saveCallbacks[i - 1];
cb.callback(settings, false, cb.private_data);
}
}
void on_preload(obs_data_t * settings) override
{
for (size_t i = preloadCallbacks.size(); i > 0; i--) {
auto cb = preloadCallbacks[i - 1];
cb.callback(settings, false, cb.private_data);
}
}
void on_save(obs_data_t * settings) override
{
for (size_t i = saveCallbacks.size(); i > 0; i--) {
auto cb = saveCallbacks[i - 1];
cb.callback(settings, true, cb.private_data);
}
}
void on_event(enum obs_frontend_event event) override
{
if (main->disableSaving &&
event !=
OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP &&
event != OBS_FRONTEND_EVENT_EXIT)
return;
for (size_t i = callbacks.size(); i > 0; i--) {
auto cb = callbacks[i - 1];
cb.callback(event, cb.private_data);
}
}
};
obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main)
{
obs_frontend_callbacks *api = new OBSStudioAPI(main);
obs_frontend_set_callbacks_internal(api);
return api;
return main->activeConfiguration;
}
config_t *obs_frontend_get_global_config(void) override
{
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,
const char *geometry,
const char *name) override
{
SavedProjectorInfo proj = {
ProjectorType::Preview,
monitor,
geometry ? geometry : "",
name ? name : "",
};
if (type) {
if (astrcmpi(type, "Source") == 0)
proj.type = ProjectorType::Source;
else if (astrcmpi(type, "Scene") == 0)
proj.type = ProjectorType::Scene;
else if (astrcmpi(type, "StudioProgram") == 0)
proj.type = ProjectorType::StudioProgram;
else if (astrcmpi(type, "Multiview") == 0)
proj.type = ProjectorType::Multiview;
}
QMetaObject::invokeMethod(main, "OpenSavedProjector",
WaitConnection(),
Q_ARG(SavedProjectorInfo *, &proj));
}
void obs_frontend_save(void) override { main->SaveProject(); }
void obs_frontend_defer_save_begin(void) override
{
QMetaObject::invokeMethod(main, "DeferSaveBegin");
}
void obs_frontend_defer_save_end(void) override
{
QMetaObject::invokeMethod(main, "DeferSaveEnd");
}
void obs_frontend_add_save_callback(obs_frontend_save_cb callback,
void *private_data) override
{
size_t idx =
GetCallbackIdx(saveCallbacks, callback, private_data);
if (idx == (size_t)-1)
saveCallbacks.emplace_back(callback, private_data);
}
void obs_frontend_remove_save_callback(obs_frontend_save_cb callback,
void *private_data) override
{
size_t idx =
GetCallbackIdx(saveCallbacks, callback, private_data);
if (idx == (size_t)-1)
return;
saveCallbacks.erase(saveCallbacks.begin() + idx);
}
void obs_frontend_add_preload_callback(obs_frontend_save_cb callback,
void *private_data) override
{
size_t idx = GetCallbackIdx(preloadCallbacks, callback,
private_data);
if (idx == (size_t)-1)
preloadCallbacks.emplace_back(callback, private_data);
}
void obs_frontend_remove_preload_callback(obs_frontend_save_cb callback,
void *private_data) override
{
size_t idx = GetCallbackIdx(preloadCallbacks, callback,
private_data);
if (idx == (size_t)-1)
return;
preloadCallbacks.erase(preloadCallbacks.begin() + idx);
}
void obs_frontend_push_ui_translation(
obs_frontend_translate_ui_cb translate) override
{
App()->PushUITranslation(translate);
}
void obs_frontend_pop_ui_translation(void) override
{
App()->PopUITranslation();
}
void obs_frontend_set_streaming_service(obs_service_t *service) override
{
main->SetService(service);
}
obs_service_t *obs_frontend_get_streaming_service(void) override
{
return main->GetService();
}
void obs_frontend_save_streaming_service(void) override
{
main->SaveService();
}
bool obs_frontend_preview_program_mode_active(void) override
{
return main->IsPreviewProgramMode();
}
void obs_frontend_set_preview_program_mode(bool enable) override
{
main->SetPreviewProgramMode(enable);
}
void obs_frontend_preview_program_trigger_transition(void) override
{
QMetaObject::invokeMethod(main, "TransitionClicked");
}
bool obs_frontend_preview_enabled(void) override
{
return main->previewEnabled;
}
void obs_frontend_set_preview_enabled(bool enable) override
{
if (main->previewEnabled != enable)
main->EnablePreviewDisplay(enable);
}
obs_source_t *obs_frontend_get_current_preview_scene(void) override
{
if (main->IsPreviewProgramMode()) {
OBSSource source = main->GetCurrentSceneSource();
return obs_source_get_ref(source);
}
return nullptr;
}
void
obs_frontend_set_current_preview_scene(obs_source_t *scene) override
{
if (main->IsPreviewProgramMode()) {
QMetaObject::invokeMethod(main, "SetCurrentScene",
Q_ARG(OBSSource,
OBSSource(scene)),
Q_ARG(bool, false));
}
}
void obs_frontend_take_screenshot(void) override
{
QMetaObject::invokeMethod(main, "Screenshot");
}
void obs_frontend_take_source_screenshot(obs_source_t *source) override
{
QMetaObject::invokeMethod(main, "Screenshot",
Q_ARG(OBSSource, OBSSource(source)));
}
obs_output_t *obs_frontend_get_virtualcam_output(void) override
{
OBSOutput output = main->outputHandler->virtualCam.Get();
return obs_output_get_ref(output);
}
void obs_frontend_start_virtualcam(void) override
{
QMetaObject::invokeMethod(main, "StartVirtualCam");
}
void obs_frontend_stop_virtualcam(void) override
{
QMetaObject::invokeMethod(main, "StopVirtualCam");
}
bool obs_frontend_virtualcam_active(void) override
{
return os_atomic_load_bool(&virtualcam_active);
}
void obs_frontend_reset_video(void) override { main->ResetVideo(); }
void obs_frontend_open_source_properties(obs_source_t *source) override
{
QMetaObject::invokeMethod(main, "OpenProperties",
Q_ARG(OBSSource, OBSSource(source)));
}
void obs_frontend_open_source_filters(obs_source_t *source) override
{
QMetaObject::invokeMethod(main, "OpenFilters",
Q_ARG(OBSSource, OBSSource(source)));
}
void obs_frontend_open_source_interaction(obs_source_t *source) override
{
QMetaObject::invokeMethod(main, "OpenInteraction",
Q_ARG(OBSSource, OBSSource(source)));
}
void obs_frontend_open_sceneitem_edit_transform(
obs_sceneitem_t *item) override
{
QMetaObject::invokeMethod(main, "OpenEditTransform",
Q_ARG(OBSSceneItem,
OBSSceneItem(item)));
}
char *obs_frontend_get_current_record_output_path(void) override
{
const char *recordOutputPath = main->GetCurrentOutputPath();
return bstrdup(recordOutputPath);
}
const char *obs_frontend_get_locale_string(const char *string) override
{
return Str(string);
}
bool obs_frontend_is_theme_dark(void) override
{
return App()->IsThemeDark();
}
char *obs_frontend_get_last_recording(void) override
{
return bstrdup(main->outputHandler->lastRecordingPath.c_str());
}
char *obs_frontend_get_last_screenshot(void) override
{
return bstrdup(main->lastScreenshot.c_str());
}
char *obs_frontend_get_last_replay(void) override
{
return bstrdup(main->lastReplay.c_str());
}
void obs_frontend_add_undo_redo_action(const char *name,
const undo_redo_cb undo,
const undo_redo_cb redo,
const char *undo_data,
const char *redo_data,
bool repeatable) override
{
main->undo_s.add_action(
name,
[undo](const std::string &data) { undo(data.c_str()); },
[redo](const std::string &data) { redo(data.c_str()); },
undo_data, redo_data, repeatable);
}
void on_load(obs_data_t *settings) override
{
for (size_t i = saveCallbacks.size(); i > 0; i--) {
auto cb = saveCallbacks[i - 1];
cb.callback(settings, false, cb.private_data);
}
}
void on_preload(obs_data_t *settings) override
{
for (size_t i = preloadCallbacks.size(); i > 0; i--) {
auto cb = preloadCallbacks[i - 1];
cb.callback(settings, false, cb.private_data);
}
}
void on_save(obs_data_t *settings) override
{
for (size_t i = saveCallbacks.size(); i > 0; i--) {
auto cb = saveCallbacks[i - 1];
cb.callback(settings, true, cb.private_data);
}
}
void on_event(enum obs_frontend_event event) override
{
if (main->disableSaving &&
event != OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP &&
event != OBS_FRONTEND_EVENT_EXIT)
return;
for (size_t i = callbacks.size(); i > 0; i--) {
auto cb = callbacks[i - 1];
cb.callback(event, cb.private_data);
}
}
};
obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main)
{
obs_frontend_callbacks *api = new OBSStudioAPI(main);
obs_frontend_set_callbacks_internal(api);
return api;
}

View File

@ -609,111 +609,43 @@ static bool MakeUserDirs()
return true;
}
constexpr std::string_view OBSProfileSubDirectory = "obs-studio/basic/profiles";
constexpr std::string_view OBSScenesSubDirectory = "obs-studio/basic/scenes";
static bool MakeUserProfileDirs()
{
char path[512];
const std::filesystem::path userProfilePath =
App()->userProfilesLocation /
std::filesystem::u8path(OBSProfileSubDirectory);
const std::filesystem::path userScenesPath =
App()->userScenesLocation /
std::filesystem::u8path(OBSScenesSubDirectory);
if (GetConfigPath(path, sizeof(path), "obs-studio/basic/profiles") <= 0)
return false;
if (!do_mkdir(path))
return false;
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;
}
}
if (GetConfigPath(path, sizeof(path), "obs-studio/basic/scenes") <= 0)
return false;
if (!do_mkdir(path))
return false;
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;
}
}
return true;
}
static string GetProfileDirFromName(const char *name)
{
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;
}
}
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;
}
}
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;
}
bool OBSApp::UpdatePre22MultiviewLayout(const char *layout)
{
if (!layout)
@ -1212,56 +1144,77 @@ 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)
if (GetAppConfigPath(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);
const std::filesystem::path basicPath = std::filesystem::u8path(path);
if (!std::filesystem::exists(basicPath)) {
return;
}
const std::filesystem::path profilesPath =
App()->userProfilesLocation /
std::filesystem::u8path("obs-studio/basic/profiles");
if (std::filesystem::exists(profilesPath)) {
return;
}
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;
}
const std::filesystem::path newProfilePath =
profilesPath / std::filesystem::u8path(Str("Untitled"));
for (auto &entry : std::filesystem::directory_iterator(basicPath)) {
if (entry.is_directory()) {
continue;
}
if (entry.path().filename().u8string() == "scenes.json") {
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;
}
}
const filesystem::path destinationFile =
newProfilePath / entry.path().filename();
const auto copyOptions =
std::filesystem::copy_options::overwrite_existing;
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)

View File

@ -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)
return OBSData();
const OBSBasic *basic =
reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
const OBSProfile &currentProfile = basic->GetCurrentProfile();
OBSDataAutoRelease data =
obs_data_create_from_json_file_safe(serviceJsonPath, "bak");
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(
jsonFilePath.u8string().c_str(), "bak");
obs_data_set_default_string(data, "type", "rtmp_common");
type = obs_data_get_string(data, "type");

View File

@ -1616,19 +1616,28 @@ struct AdvancedOutput : BasicOutputHandler {
static OBSData GetDataFromJsonFile(const char *jsonFile)
{
char fullPath[512];
const OBSBasic *basic =
reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
const OBSProfile &currentProfile = 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<char> jsonData = os_quick_read_utf8_file(fullPath);
if (!jsonFilePath.empty()) {
BPtr<char> 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();
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -134,6 +134,37 @@ private:
std::shared_ptr<OBSSignal> 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<bool(const OBSPromptResult &result)>;
using OBSProfileCache = std::map<std::string, OBSProfile>;
class ColorSelect : public QWidget {
public:
@ -443,17 +474,6 @@ private:
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();
@ -742,11 +762,6 @@ public slots:
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();
@ -1156,13 +1171,6 @@ private slots:
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 +1345,59 @@ public:
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<std::string>
GetRestartRequirements(const ConfigFile &config) const;
void ResetProfileData();
void CheckForSimpleModeX264Fallback();
public:
inline const OBSProfileCache &GetProfileCache() const noexcept
{
return profiles;
};
const OBSProfile &GetCurrentProfile() const;
std::optional<OBSProfile>
GetProfileByName(const std::string &profileName) const;
std::optional<OBSProfile>
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);
};
extern bool cef_js_avail;

View File

@ -2136,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<OBSBasic *>(App()->GetMainWindow());
const OBSProfile &currentProfile = 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);
}
@ -3748,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<OBSBasic *>(App()->GetMainWindow());
const OBSProfile &currentProfile = 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");
}
}
}
@ -5691,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 &currentProfile = 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);
}
}

View File

@ -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