diff --git a/libobs/obs-scene.c b/libobs/obs-scene.c index fdb5a6ded..9cb556037 100644 --- a/libobs/obs-scene.c +++ b/libobs/obs-scene.c @@ -19,6 +19,12 @@ #include "graphics/math-defs.h" #include "obs-scene.h" +static const char *obs_scene_signals[] = { + "void item_add(ptr scene, ptr item)", + "void item_remove(ptr scene, ptr item)", + NULL +}; + static inline void signal_item_remove(struct obs_scene_item *item) { struct calldata params = {0}; @@ -32,8 +38,9 @@ static inline void signal_item_remove(struct obs_scene_item *item) static const char *scene_getname(const char *locale) { + /* TODO: locale */ UNUSED_PARAMETER(locale); - return "Scene internal source type"; + return "Scene"; } static void *scene_create(obs_data_t settings, struct obs_source *source) @@ -43,6 +50,9 @@ static void *scene_create(obs_data_t settings, struct obs_source *source) scene->source = source; scene->first_item = NULL; + signal_handler_add_array(obs_source_signalhandler(source), + obs_scene_signals); + if (pthread_mutexattr_init(&attr) != 0) goto fail; if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0) @@ -61,9 +71,8 @@ fail: return NULL; } -static void scene_destroy(void *data) +static void remove_all_items(struct obs_scene *scene) { - struct obs_scene *scene = data; struct obs_scene_item *item; pthread_mutex_lock(&scene->mutex); @@ -78,7 +87,13 @@ static void scene_destroy(void *data) } pthread_mutex_unlock(&scene->mutex); +} +static void scene_destroy(void *data) +{ + struct obs_scene *scene = data; + + remove_all_items(scene); pthread_mutex_destroy(&scene->mutex); bfree(scene); } @@ -173,6 +188,85 @@ static void scene_video_render(void *data, effect_t effect) UNUSED_PARAMETER(effect); } +static void scene_load_item(struct obs_scene *scene, obs_data_t item_data) +{ + const char *name = obs_data_getstring(item_data, "name"); + obs_source_t source = obs_get_source_by_name(name); + struct obs_scene_item *item; + + if (!source) { + blog(LOG_WARNING, "[scene_load_item] Source %s not found!", + name); + return; + } + + item = obs_scene_add(scene, source); + + item->rot = (float)obs_data_getdouble(item_data, "rot"); + item->visible = obs_data_getbool(item_data, "visible"); + obs_data_get_vec2(item_data, "origin", &item->origin); + obs_data_get_vec2(item_data, "pos", &item->pos); + obs_data_get_vec2(item_data, "scale", &item->scale); + obs_source_release(source); +} + +static void scene_load(void *scene, obs_data_t settings) +{ + obs_data_array_t items = obs_data_getarray(settings, "items"); + size_t count, i; + + remove_all_items(scene); + + if (!items) return; + + count = obs_data_array_count(items); + + for (i = 0; i < count; i++) { + obs_data_t item_data = obs_data_array_item(items, i); + scene_load_item(scene, item_data); + obs_data_release(item_data); + } + + obs_data_array_release(items); +} + +static void scene_save_item(struct obs_scene *scene, obs_data_array_t array, + struct obs_scene_item *item) +{ + obs_data_t item_data = obs_data_create(); + const char *name = obs_source_getname(item->source); + + obs_data_setstring(item_data, "name", name); + obs_data_setbool (item_data, "visible", item->visible); + obs_data_setdouble(item_data, "rot", item->rot); + obs_data_set_vec2 (item_data, "origin", &item->origin); + obs_data_set_vec2 (item_data, "pos", &item->pos); + obs_data_set_vec2 (item_data, "scale", &item->scale); + + obs_data_array_push_back(array, item_data); + obs_data_release(item_data); +} + +static void scene_save(void *data, obs_data_t settings) +{ + struct obs_scene *scene = data; + obs_data_array_t array = obs_data_array_create(); + struct obs_scene_item *item; + + pthread_mutex_lock(&scene->mutex); + + item = scene->first_item; + while (item) { + scene_save_item(scene, array, item); + item = item->next; + } + + pthread_mutex_unlock(&scene->mutex); + + obs_data_setarray(settings, "items", array); + obs_data_array_release(array); +} + static uint32_t scene_getwidth(void *data) { UNUSED_PARAMETER(data); @@ -185,10 +279,10 @@ static uint32_t scene_getheight(void *data) return obs->video.base_height; } -static const struct obs_source_info scene_info = +const struct obs_source_info scene_info = { .id = "scene", - .type = OBS_SOURCE_TYPE_SCENE, + .type = OBS_SOURCE_TYPE_INPUT, .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW, .getname = scene_getname, .create = scene_create, @@ -196,41 +290,16 @@ static const struct obs_source_info scene_info = .video_render = scene_video_render, .getwidth = scene_getwidth, .getheight = scene_getheight, + .load = scene_load, + .save = scene_save, .enum_sources = scene_enum_sources }; -static const char *obs_scene_signals[] = { - "void item_add(ptr scene, ptr item)", - "void item_remove(ptr scene, ptr item)", - NULL -}; - obs_scene_t obs_scene_create(const char *name) { - struct obs_source *source = bzalloc(sizeof(struct obs_source)); - struct obs_scene *scene; - - if (!obs_source_init_context(source, NULL, name)) { - bfree(source); - return NULL; - } - - signal_handler_add_array(source->context.signals, obs_scene_signals); - - scene = scene_create(source->context.settings, source); - source->context.data = scene; - - assert(scene); - if (!scene) { - obs_context_data_free(&source->context); - bfree(source); - return NULL; - } - - scene->source = source; - obs_source_init(source, &scene_info); - memcpy(&source->info, &scene_info, sizeof(struct obs_source_info)); - return scene; + struct obs_source *source = + obs_source_create(OBS_SOURCE_TYPE_INPUT, "scene", name, NULL); + return source->context.data; } void obs_scene_addref(obs_scene_t scene) @@ -252,7 +321,7 @@ obs_source_t obs_scene_getsource(obs_scene_t scene) obs_scene_t obs_scene_fromsource(obs_source_t source) { - if (!source || source->info.type != OBS_SOURCE_TYPE_SCENE) + if (!source || source->info.id != scene_info.id) return NULL; return source->context.data; diff --git a/libobs/obs-source.c b/libobs/obs-source.c index df0e1fce7..2d9c493ab 100644 --- a/libobs/obs-source.c +++ b/libobs/obs-source.c @@ -60,11 +60,6 @@ static const struct obs_source_info *get_source_info(enum obs_source_type type, case OBS_SOURCE_TYPE_TRANSITION: list = &obs->transition_types.da; break; - - case OBS_SOURCE_TYPE_SCENE: - default: - blog(LOG_ERROR, "get_source_info: invalid source type"); - return NULL; } return find_source(list, id); @@ -1737,3 +1732,15 @@ void obs_transition_end_frame(obs_source_t transition) if (!transition) return; obs_source_enum_tree(transition, apply_transition_vol, NULL); } + +void obs_source_save(obs_source_t source) +{ + if (!source || !source->info.save) return; + source->info.save(source->context.data, source->context.settings); +} + +void obs_source_load(obs_source_t source) +{ + if (!source || !source->info.load) return; + source->info.load(source->context.data, source->context.settings); +} diff --git a/libobs/obs-source.h b/libobs/obs-source.h index 1dd297059..5f40d8799 100644 --- a/libobs/obs-source.h +++ b/libobs/obs-source.h @@ -28,8 +28,6 @@ enum obs_source_type { OBS_SOURCE_TYPE_INPUT, OBS_SOURCE_TYPE_FILTER, OBS_SOURCE_TYPE_TRANSITION, - - OBS_SOURCE_TYPE_SCENE = 0x80000000 }; @@ -261,6 +259,27 @@ struct obs_source_info { void (*enum_sources)(void *data, obs_source_enum_proc_t enum_callback, void *param); + + /** + * Called when saving a source. This is a separate function because + * sometimes a source needs to know when it is being saved so it + * doesn't always have to update the current settings until a certain + * point. + * + * @param data Source data + * @param settings Settings + */ + void (*save)(void *data, obs_data_t settings); + + /** + * Called when loading a source from saved data. This should be called + * after all the loading sources have actually been created because + * sometimes there are sources that depend on each other. + * + * @param data Source data + * @param settings Settings + */ + void (*load)(void *data, obs_data_t settings); }; EXPORT void obs_register_source_s(const struct obs_source_info *info, diff --git a/libobs/obs.c b/libobs/obs.c index 59fb27623..645c1246d 100644 --- a/libobs/obs.c +++ b/libobs/obs.c @@ -491,12 +491,19 @@ static inline bool obs_init_handlers(void) return signal_handler_add_array(obs->signals, obs_signals); } +extern const struct obs_source_info scene_info; + static bool obs_init(void) { obs = bzalloc(sizeof(struct obs_core)); - obs_init_data(); - return obs_init_handlers(); + if (!obs_init_data()) + return false; + if (!obs_init_handlers()) + return false; + + obs_register_source(&scene_info); + return true; } bool obs_startup(void) @@ -1026,6 +1033,82 @@ float obs_get_present_volume(void) return obs ? obs->audio.present_volume : 0.0f; } +void obs_load_sources(obs_data_array_t array) +{ + size_t count; + size_t i; + + if (!obs) return; + + count = obs_data_array_count(array); + + pthread_mutex_lock(&obs->data.user_sources_mutex); + + for (i = 0; i < count; i++) { + obs_data_t source_data = obs_data_array_item(array, i); + + const char *name = obs_data_getstring(source_data, "name"); + const char *id = obs_data_getstring(source_data, "id"); + obs_data_t settings = obs_data_getobj(source_data, "settings"); + obs_source_t source; + + source = obs_source_create(OBS_SOURCE_TYPE_INPUT, id, name, + settings); + obs_add_source(source); + obs_source_release(source); + + obs_data_release(settings); + obs_data_release(source_data); + } + + /* tell sources that we want to load */ + for (i = 0; i < obs->data.user_sources.num; i++) + obs_source_load(obs->data.user_sources.array[i]); + + pthread_mutex_unlock(&obs->data.user_sources_mutex); +} + +static void save_source_data(obs_data_array_t array, obs_source_t source) +{ + obs_data_t source_data = obs_data_create(); + obs_data_t settings = obs_source_getsettings(source); + const char *name = obs_source_getname(source); + const char *id; + + obs_source_gettype(source, NULL, &id); + + obs_data_setstring(source_data, "name", name); + obs_data_setstring(source_data, "id", id); + obs_data_setobj (source_data, "settings", settings); + + obs_data_array_push_back(array, source_data); + + obs_data_release(source_data); + obs_data_release(settings); +} + +obs_data_array_t obs_save_sources(void) +{ + obs_data_array_t array; + size_t i; + + if (!obs) return NULL; + + array = obs_data_array_create(); + + pthread_mutex_lock(&obs->data.user_sources_mutex); + + for (i = 0; i < obs->data.user_sources.num; i++) { + obs_source_t source = obs->data.user_sources.array[i]; + obs_source_save(source); + save_source_data(array, source); + } + + pthread_mutex_unlock(&obs->data.user_sources_mutex); + + return array; +} + /* ensures that names are never blank */ static inline char *dup_name(const char *name) { diff --git a/libobs/obs.h b/libobs/obs.h index 4f59b7709..7623c1248 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -357,6 +357,12 @@ EXPORT float obs_get_master_volume(void); /** Gets the master presentation volume */ EXPORT float obs_get_present_volume(void); +/** Loads sources from a data array */ +EXPORT void obs_load_sources(obs_data_array_t array); + +/** Saves sources to a data array */ +EXPORT obs_data_array_t obs_save_sources(void); + /* ------------------------------------------------------------------------- */ /* View context */ @@ -547,6 +553,22 @@ EXPORT void obs_source_enum_tree(obs_source_t source, /** Returns true if active, false if not */ EXPORT bool obs_source_active(obs_source_t source); +/** + * Sometimes sources need to be told when to save their settings so they + * don't have to constantly update and keep track of their settings. This will + * call the source's 'save' callback if any, which will save its current + * data to its settings. + */ +EXPORT void obs_source_save(obs_source_t source); + +/** + * Sometimes sources need to be told when they are loading their settings + * from prior saved data. This is different from a source 'update' in that + * it's meant to be used after the source has been created and loaded from + * somewhere (such as a saved file). + */ +EXPORT void obs_source_load(obs_source_t source); + /* ------------------------------------------------------------------------- */ /* Functions used by sources */ diff --git a/obs/window-basic-main.cpp b/obs/window-basic-main.cpp index c0e175187..36ce3a736 100644 --- a/obs/window-basic-main.cpp +++ b/obs/window-basic-main.cpp @@ -66,6 +66,59 @@ OBSBasic::OBSBasic(QWidget *parent) }); } +static obs_data_t GenerateSaveData() +{ + obs_data_t saveData = obs_data_create(); + obs_data_array_t sourcesArray = obs_save_sources(); + obs_source_t currentScene = obs_get_output_source(0); + const char *sceneName = obs_source_getname(currentScene); + + obs_data_setstring(saveData, "current_scene", sceneName); + obs_data_setarray(saveData, "sources", sourcesArray); + obs_data_array_release(sourcesArray); + obs_source_release(currentScene); + + return saveData; +} + +void OBSBasic::Save(const char *file) +{ + obs_data_t saveData = GenerateSaveData(); + const char *jsonData = obs_data_getjson(saveData); + + /* TODO maybe a message box here? */ + if (!os_quick_write_utf8_file(file, jsonData, strlen(jsonData), false)) + blog(LOG_ERROR, "Could not save scene data to %s", file); + + obs_data_release(saveData); +} + +void OBSBasic::Load(const char *file) +{ + if (!file) { + blog(LOG_ERROR, "Could not find file %s", file); + return; + } + + BPtr jsonData = os_quick_read_utf8_file(file); + if (!jsonData) + return; + + obs_data_t data = obs_data_create_from_json(jsonData); + obs_data_array_t sources = obs_data_getarray(data, "sources"); + const char *sceneName = obs_data_getstring(data, "current_scene"); + obs_source_t curScene; + + obs_load_sources(sources); + + curScene = obs_get_source_by_name(sceneName); + obs_set_output_source(0, curScene); + obs_source_release(curScene); + + obs_data_array_release(sources); + obs_data_release(data); +} + static inline bool HasAudioDevices(const char *source_id) { const char *output_id = source_id; @@ -310,12 +363,17 @@ void OBSBasic::OBSInit() if (!InitService()) throw "Failed to initialize service"; + BPtr savePath(os_get_config_path("obs-studio/basic/scenes.json")); + Load(savePath); + ResetAudioDevices(); } OBSBasic::~OBSBasic() { + BPtr savePath(os_get_config_path("obs-studio/basic/scenes.json")); SaveService(); + Save(savePath); if (properties) delete properties; @@ -441,12 +499,12 @@ void OBSBasic::UpdateSceneSelection(OBSSource source) obs_source_type type; obs_source_gettype(source, &type, NULL); - if (type != OBS_SOURCE_TYPE_SCENE) - return; - obs_scene_t scene = obs_scene_fromsource(source); const char *name = obs_source_getname(source); + if (!scene) + return; + QList items = ui->scenes->findItems(QT_UTF8(name), Qt::MatchExactly); @@ -486,10 +544,7 @@ void OBSBasic::SourceAdded(void *data, calldata_t params) { obs_source_t source = (obs_source_t)calldata_ptr(params, "source"); - obs_source_type type; - obs_source_gettype(source, &type, NULL); - - if (type == OBS_SOURCE_TYPE_SCENE) + if (obs_scene_fromsource(source) != NULL) QMetaObject::invokeMethod(static_cast(data), "AddScene", Q_ARG(OBSSource, OBSSource(source))); @@ -499,10 +554,7 @@ void OBSBasic::SourceRemoved(void *data, calldata_t params) { obs_source_t source = (obs_source_t)calldata_ptr(params, "source"); - obs_source_type type; - obs_source_gettype(source, &type, NULL); - - if (type == OBS_SOURCE_TYPE_SCENE) + if (obs_scene_fromsource(source) != NULL) QMetaObject::invokeMethod(static_cast(data), "RemoveScene", Q_ARG(OBSSource, OBSSource(source))); @@ -906,6 +958,9 @@ void OBSBasic::AddSourcePopupMenu(const QPoint &pos) OBS_SOURCE_TYPE_INPUT, type, App()->GetLocale()); + if (strcmp(type, "scene") == 0) + continue; + QAction *popupItem = new QAction(QT_UTF8(name), this); popupItem->setData(QT_UTF8(type)); popup.addAction(popupItem); diff --git a/obs/window-basic-main.hpp b/obs/window-basic-main.hpp index d0c4730a9..cea1e83db 100644 --- a/obs/window-basic-main.hpp +++ b/obs/window-basic-main.hpp @@ -52,6 +52,9 @@ private: QPointer properties; + void Save(const char *file); + void Load(const char *file); + void SaveService(); bool LoadService();