浏览代码

libobs: Add core logic for phase 1 plugin manager

Phase 1 of the plugin manager provides the the ability to toggle off/on
plugins loading when OBS starts. Additionally, it implements loading of
a manifest file for plugins that allows plugin authors to provide more
detailed information about the plugin including authors, support site,
name, description.

In order to accomplish this, this change updates libobs to provide
more detailed tracking of modules- specifically tracking both enabled
and disabled modules, alone with a module load state which indicates
why a module is not loaded. Additionally, changes were made to establish
a links between a module and any features (inputs, outputs, encoders,
and services) it provides (and thus the ability to determine why a
feature might not be enabled). Along with these changes to modules,
this commit also provides an indicator and lookup for core modules which
can not be disabled by the plugin manager.

Finally, this change provides functions to properly load and retrieve
a standardized plugin metadata file.
FiniteSingularity 1 月之前
父节点
当前提交
14004cec96
共有 13 个文件被更改,包括 575 次插入10 次删除
  1. 34 0
      libobs/obs-encoder.c
  2. 3 0
      libobs/obs-encoder.h
  3. 58 0
      libobs/obs-internal.h
  4. 262 8
      libobs/obs-module.c
  5. 6 0
      libobs/obs-module.h
  6. 34 0
      libobs/obs-output.c
  7. 3 0
      libobs/obs-output.h
  8. 34 0
      libobs/obs-service.c
  9. 3 0
      libobs/obs-service.h
  10. 34 0
      libobs/obs-source.c
  11. 3 0
      libobs/obs-source.h
  12. 22 2
      libobs/obs.c
  13. 79 0
      libobs/obs.h

+ 34 - 0
libobs/obs-encoder.c

@@ -44,6 +44,40 @@ const char *obs_encoder_get_display_name(const char *id)
 	return ei ? ei->get_name(ei->type_data) : NULL;
 }
 
+obs_module_t *obs_encoder_get_module(const char *id)
+{
+	obs_module_t *module = obs->first_module;
+	while (module) {
+		for (size_t i = 0; i < module->encoders.num; i++) {
+			if (strcmp(module->encoders.array[i], id) == 0) {
+				return module;
+			}
+		}
+		module = module->next;
+	}
+
+	module = obs->first_disabled_module;
+	while (module) {
+		for (size_t i = 0; i < module->encoders.num; i++) {
+			if (strcmp(module->encoders.array[i], id) == 0) {
+				return module;
+			}
+		}
+		module = module->next;
+	}
+
+	return NULL;
+}
+
+enum obs_module_load_state obs_encoder_load_state(const char *id)
+{
+	obs_module_t *module = obs_encoder_get_module(id);
+	if (!module) {
+		return OBS_MODULE_MISSING;
+	}
+	return module->load_state;
+}
+
 static bool init_encoder(struct obs_encoder *encoder, const char *name, obs_data_t *settings, obs_data_t *hotkey_data)
 {
 	pthread_mutex_init_value(&encoder->init_mutex);

+ 3 - 0
libobs/obs-encoder.h

@@ -344,6 +344,9 @@ struct obs_encoder_info {
 
 	bool (*encode_texture2)(void *data, struct encoder_texture *texture, int64_t pts, uint64_t lock_key,
 				uint64_t *next_key, struct encoder_packet *packet, bool *received_packet);
+
+	/** Pointer to module that generated this encoder **/
+	obs_module_t *module;
 };
 
 EXPORT void obs_register_encoder_s(const struct obs_encoder_info *info, size_t size);

+ 58 - 0
libobs/obs-internal.h

@@ -113,6 +113,8 @@ struct obs_module {
 	void *module;
 	bool loaded;
 
+	enum obs_module_load_state load_state;
+
 	bool (*load)(void);
 	void (*unload)(void);
 	void (*post_load)(void);
@@ -125,7 +127,28 @@ struct obs_module {
 	const char *(*description)(void);
 	const char *(*author)(void);
 
+	struct obs_module_metadata *metadata;
+
 	struct obs_module *next;
+
+	DARRAY(char *) sources;
+	DARRAY(char *) outputs;
+	DARRAY(char *) encoders;
+	DARRAY(char *) services;
+};
+
+struct obs_disabled_module {
+	char *mod_name;
+
+	enum obs_module_load_state load_state;
+
+	struct obs_module_metadata *metadata;
+	struct obs_disabled_module *next;
+
+	DARRAY(char *) sources;
+	DARRAY(char *) outputs;
+	DARRAY(char *) encoders;
+	DARRAY(char *) services;
 };
 
 extern void free_module(struct obs_module *mod);
@@ -143,6 +166,37 @@ static inline void free_module_path(struct obs_module_path *omp)
 	}
 }
 
+struct obs_module_metadata {
+	char *display_name;
+	char *version;
+	char *id;
+	char *os_arch;
+	char *description;
+	char *long_description;
+	bool has_icon;
+	bool has_banner;
+	char *repository_url;
+	char *support_url;
+	char *website_url;
+	char *name;
+};
+
+static inline void free_module_metadata(struct obs_module_metadata *omi)
+{
+	if (omi) {
+		bfree(omi->display_name);
+		bfree(omi->version);
+		bfree(omi->id);
+		bfree(omi->os_arch);
+		bfree(omi->description);
+		bfree(omi->long_description);
+		bfree(omi->repository_url);
+		bfree(omi->support_url);
+		bfree(omi->website_url);
+		bfree(omi->name);
+	}
+}
+
 static inline bool check_path(const char *data, const char *path, struct dstr *output)
 {
 	dstr_copy(output, path);
@@ -490,8 +544,12 @@ typedef DARRAY(struct obs_source_info) obs_source_info_array_t;
 
 struct obs_core {
 	struct obs_module *first_module;
+	struct obs_module *first_disabled_module;
+
 	DARRAY(struct obs_module_path) module_paths;
 	DARRAY(char *) safe_modules;
+	DARRAY(char *) disabled_modules;
+	DARRAY(char *) core_modules;
 
 	obs_source_info_array_t source_types;
 	obs_source_info_array_t input_types;

+ 262 - 8
libobs/obs-module.c

@@ -24,6 +24,8 @@
 
 extern const char *get_module_extension(void);
 
+obs_module_t *loadingModule = NULL;
+
 static inline int req_func_not_found(const char *name, const char *path)
 {
 	blog(LOG_DEBUG,
@@ -94,6 +96,47 @@ static inline char *get_module_name(const char *file)
 extern void reset_win32_symbol_paths(void);
 #endif
 
+int obs_module_load_metadata(struct obs_module *mod)
+{
+	struct obs_module_metadata *md = NULL;
+
+	/* Check if the metadata file exists */
+	struct dstr path = {0};
+
+	dstr_copy(&path, mod->data_path);
+	if (!dstr_is_empty(&path) && dstr_end(&path) != '/') {
+		dstr_cat_ch(&path, '/');
+	}
+	dstr_cat(&path, "manifest.json");
+
+	if (os_file_exists(path.array)) {
+		/* If we find a metadata file, allocate a new metadata. */
+		md = bmalloc(sizeof(obs_module_metadata_t));
+		obs_data_t *metadata = obs_data_create_from_json_file(path.array);
+
+		md->display_name = bstrdup(obs_data_get_string(metadata, "display_name"));
+		md->id = bstrdup(obs_data_get_string(metadata, "id"));
+		md->version = bstrdup(obs_data_get_string(metadata, "version"));
+		md->os_arch = bstrdup(obs_data_get_string(metadata, "os_arch"));
+		md->name = bstrdup(obs_data_get_string(metadata, "name"));
+		md->description = bstrdup(obs_data_get_string(metadata, "description"));
+		md->long_description = bstrdup(obs_data_get_string(metadata, "long_description"));
+
+		obs_data_t *urls = obs_data_get_obj(metadata, "urls");
+		md->repository_url = bstrdup(obs_data_get_string(urls, "repository"));
+		md->website_url = bstrdup(obs_data_get_string(urls, "website"));
+		md->support_url = bstrdup(obs_data_get_string(urls, "support"));
+		obs_data_release(urls);
+
+		md->has_banner = obs_data_get_bool(metadata, "has_banner");
+		md->has_icon = obs_data_get_bool(metadata, "has_icon");
+		obs_data_release(metadata);
+	}
+	dstr_free(&path);
+	mod->metadata = md;
+	return MODULE_SUCCESS;
+}
+
 int obs_open_module(obs_module_t **module, const char *path, const char *data_path)
 {
 	struct obs_module mod = {0};
@@ -138,11 +181,19 @@ int obs_open_module(obs_module_t **module, const char *path, const char *data_pa
 	mod.mod_name = get_module_name(mod.file);
 	mod.data_path = bstrdup(data_path);
 	mod.next = obs->first_module;
+	mod.load_state = OBS_MODULE_ENABLED;
+
+	da_init(mod.sources);
+	da_init(mod.outputs);
+	da_init(mod.encoders);
+	da_init(mod.services);
 
 	if (mod.file) {
 		blog(LOG_DEBUG, "Loading module: %s", mod.file);
 	}
 
+	obs_module_load_metadata(&mod);
+
 	*module = bmemdup(&mod, sizeof(mod));
 	obs->first_module = (*module);
 	mod.set_pointer(*module);
@@ -153,6 +204,32 @@ int obs_open_module(obs_module_t **module, const char *path, const char *data_pa
 	return MODULE_SUCCESS;
 }
 
+bool obs_create_disabled_module(obs_module_t **module, const char *path, const char *data_path,
+				enum obs_module_load_state state)
+{
+	struct obs_module mod = {0};
+
+	mod.bin_path = bstrdup(path);
+	mod.file = strrchr(mod.bin_path, '/');
+	mod.file = (!mod.file) ? mod.bin_path : (mod.file + 1);
+	mod.mod_name = get_module_name(mod.file);
+	mod.data_path = bstrdup(data_path);
+	mod.next = obs->first_disabled_module;
+	mod.load_state = state;
+
+	da_init(mod.sources);
+	da_init(mod.outputs);
+	da_init(mod.encoders);
+	da_init(mod.services);
+
+	obs_module_load_metadata(&mod);
+
+	*module = bmemdup(&mod, sizeof(mod));
+	obs->first_disabled_module = (*module);
+
+	return true;
+}
+
 bool obs_init_module(obs_module_t *module)
 {
 	if (!module || !obs)
@@ -164,7 +241,10 @@ bool obs_init_module(obs_module_t *module)
 		profile_store_name(obs_get_profiler_name_store(), "obs_init_module(%s)", module->file);
 	profile_start(profile_name);
 
+	loadingModule = module;
 	module->loaded = module->load();
+	loadingModule = NULL;
+
 	if (!module->loaded)
 		blog(LOG_WARNING, "Failed to initialize module '%s'", module->file);
 
@@ -187,6 +267,10 @@ const char *obs_get_module_file_name(obs_module_t *module)
 
 const char *obs_get_module_name(obs_module_t *module)
 {
+	if (module && module->metadata && module->metadata->display_name) {
+		return module->metadata->display_name;
+	}
+
 	return (module && module->name) ? module->name() : NULL;
 }
 
@@ -210,6 +294,48 @@ const char *obs_get_module_data_path(obs_module_t *module)
 	return module ? module->data_path : NULL;
 }
 
+const char *obs_get_module_id(obs_module_t *module)
+{
+	return module && module->metadata ? module->metadata->id : NULL;
+}
+
+const char *obs_get_module_version(obs_module_t *module)
+{
+	return module && module->metadata ? module->metadata->version : NULL;
+}
+
+void obs_module_add_source(obs_module_t *module, const char *id)
+{
+	char *source_id = bstrdup(id);
+	if (module) {
+		da_push_back(module->sources, &source_id);
+	}
+}
+
+void obs_module_add_output(obs_module_t *module, const char *id)
+{
+	char *output_id = bstrdup(id);
+	if (module) {
+		da_push_back(module->outputs, &output_id);
+	}
+}
+
+void obs_module_add_encoder(obs_module_t *module, const char *id)
+{
+	char *encoder_id = bstrdup(id);
+	if (module) {
+		da_push_back(module->encoders, &encoder_id);
+	}
+}
+
+void obs_module_add_service(obs_module_t *module, const char *id)
+{
+	char *service_id = bstrdup(id);
+	if (module) {
+		da_push_back(module->services, &service_id);
+	}
+}
+
 obs_module_t *obs_get_module(const char *name)
 {
 	obs_module_t *module = obs->first_module;
@@ -224,6 +350,20 @@ obs_module_t *obs_get_module(const char *name)
 	return NULL;
 }
 
+obs_module_t *obs_get_disabled_module(const char *name)
+{
+	obs_module_t *module = obs->first_disabled_module;
+	while (module) {
+		if (strcmp(module->mod_name, name) == 0) {
+			return module;
+		}
+
+		module = module->next;
+	}
+
+	return NULL;
+}
+
 void *obs_get_module_lib(obs_module_t *module)
 {
 	return module ? module->module : NULL;
@@ -284,6 +424,24 @@ void obs_add_safe_module(const char *name)
 	da_push_back(obs->safe_modules, &item);
 }
 
+void obs_add_core_module(const char *name)
+{
+	if (!obs || !name)
+		return;
+
+	char *item = bstrdup(name);
+	da_push_back(obs->core_modules, &item);
+}
+
+void obs_add_disabled_module(const char *name)
+{
+	if (!obs || !name)
+		return;
+
+	char *item = bstrdup(name);
+	da_push_back(obs->disabled_modules, &item);
+}
+
 extern void get_plugin_info(const char *path, bool *is_obs_plugin, bool *can_load);
 
 struct fail_info {
@@ -304,10 +462,39 @@ static bool is_safe_module(const char *name)
 	return false;
 }
 
+static bool is_core_module(const char *name)
+{
+	for (size_t i = 0; i < obs->core_modules.num; i++) {
+		if (strcmp(name, obs->core_modules.array[i]) == 0)
+			return true;
+	}
+
+	return false;
+}
+
+static bool is_disabled_module(const char *name)
+{
+	if (obs->disabled_modules.num == 0)
+		return false;
+
+	for (size_t i = 0; i < obs->disabled_modules.num; i++) {
+		if (strcmp(name, obs->disabled_modules.array[i]) == 0)
+			return true;
+	}
+
+	return false;
+}
+
+bool obs_get_module_allow_disable(const char *name)
+{
+	return !is_core_module(name);
+}
+
 static void load_all_callback(void *param, const struct obs_module_info2 *info)
 {
 	struct fail_info *fail_info = param;
 	obs_module_t *module;
+	obs_module_t *disabled_module;
 
 	bool is_obs_plugin;
 	bool can_load_obs_plugin;
@@ -320,10 +507,17 @@ static void load_all_callback(void *param, const struct obs_module_info2 *info)
 	}
 
 	if (!is_safe_module(info->name)) {
+		obs_create_disabled_module(&disabled_module, info->bin_path, info->data_path, OBS_MODULE_DISABLED_SAFE);
 		blog(LOG_WARNING, "Skipping module '%s', not on safe list", info->name);
 		return;
 	}
 
+	if (is_disabled_module(info->name)) {
+		obs_create_disabled_module(&disabled_module, info->bin_path, info->data_path, OBS_MODULE_DISABLED);
+		blog(LOG_WARNING, "Skipping module '%s', is disabled", info->name);
+		return;
+	}
+
 	if (!can_load_obs_plugin) {
 		blog(LOG_WARNING,
 		     "Skipping module '%s' due to possible "
@@ -350,8 +544,10 @@ static void load_all_callback(void *param, const struct obs_module_info2 *info)
 		return;
 	}
 
-	if (!obs_init_module(module))
+	if (!obs_init_module(module)) {
 		free_module(module);
+		obs_create_disabled_module(&disabled_module, info->bin_path, info->data_path, OBS_MODULE_ERROR);
+	}
 
 	UNUSED_PARAMETER(param);
 	return;
@@ -602,19 +798,57 @@ void free_module(struct obs_module *mod)
 		/* os_dlclose(mod->module); */
 	}
 
-	for (obs_module_t *m = obs->first_module; !!m; m = m->next) {
-		if (m->next == mod) {
-			m->next = mod->next;
-			break;
+	/* Is this module an active / loaded module, or a disabled module? */
+	if (mod->load_state == OBS_MODULE_ENABLED) {
+		for (obs_module_t *m = obs->first_module; !!m; m = m->next) {
+			if (m->next == mod) {
+				m->next = mod->next;
+				break;
+			}
+		}
+
+		if (obs->first_module == mod)
+			obs->first_module = mod->next;
+	} else {
+		for (obs_module_t *m = obs->first_disabled_module; !!m; m = m->next) {
+			if (m->next == mod) {
+				m->next = mod->next;
+				break;
+			}
 		}
-	}
 
-	if (obs->first_module == mod)
-		obs->first_module = mod->next;
+		if (obs->first_disabled_module == mod)
+			obs->first_disabled_module = mod->next;
+	}
 
 	bfree(mod->mod_name);
 	bfree(mod->bin_path);
 	bfree(mod->data_path);
+
+	for (size_t i = 0; i < mod->sources.num; i++) {
+		bfree(mod->sources.array[i]);
+	}
+	da_free(mod->sources);
+
+	for (size_t i = 0; i < mod->outputs.num; i++) {
+		bfree(mod->outputs.array[i]);
+	}
+	da_free(mod->outputs);
+
+	for (size_t i = 0; i < mod->encoders.num; i++) {
+		bfree(mod->encoders.array[i]);
+	}
+	da_free(mod->encoders);
+
+	for (size_t i = 0; i < mod->services.num; i++) {
+		bfree(mod->services.array[i]);
+	}
+	da_free(mod->services);
+
+	if (mod->metadata) {
+		free_module_metadata(mod->metadata);
+		bfree(mod->metadata);
+	}
 	bfree(mod);
 }
 
@@ -754,6 +988,13 @@ void obs_register_source_s(const struct obs_source_info *info, size_t size)
 		goto error;
 	}
 
+	/* NOTE: The assignment of data.module must occur before memcpy! */
+	if (loadingModule) {
+		data.module = loadingModule;
+		char *source_id = bstrdup(info->id);
+		da_push_back(loadingModule->sources, &source_id);
+	}
+
 	memcpy(&data, info, size);
 
 	/* mark audio-only filters as an async filter categorically */
@@ -875,6 +1116,7 @@ void obs_register_output_s(const struct obs_output_info *info, size_t size)
 		}
 		strlist_free(protocols);
 	}
+
 	return;
 
 error:
@@ -911,6 +1153,12 @@ void obs_register_encoder_s(const struct obs_encoder_info *info, size_t size)
 #undef CHECK_REQUIRED_VAL_
 
 	REGISTER_OBS_DEF(size, obs_encoder_info, obs->encoder_types, info);
+
+	if (loadingModule) {
+		char *encoder_id = bstrdup(info->id);
+		da_push_back(loadingModule->encoders, &encoder_id);
+	}
+
 	return;
 
 error:
@@ -934,6 +1182,12 @@ void obs_register_service_s(const struct obs_service_info *info, size_t size)
 #undef CHECK_REQUIRED_VAL_
 
 	REGISTER_OBS_DEF(size, obs_service_info, obs->service_types, info);
+
+	if (loadingModule) {
+		char *service_id = bstrdup(info->id);
+		da_push_back(loadingModule->services, &service_id);
+	}
+
 	return;
 
 error:

+ 6 - 0
libobs/obs-module.h

@@ -179,3 +179,9 @@ MODULE_EXPORT const char *obs_module_name(void);
 
 /** Optional: Returns a description of the module */
 MODULE_EXPORT const char *obs_module_description(void);
+
+/** Returns the module's unique ID, or null if it doesn't have one */
+MODULE_EXPORT const char *obs_get_module_id(obs_module_t *module);
+
+/** Returns the module's semver verison number or null if it doesn't have one */
+MODULE_EXPORT const char *obs_get_module_version(obs_module_t *module);

+ 34 - 0
libobs/obs-output.c

@@ -135,6 +135,40 @@ const char *obs_output_get_display_name(const char *id)
 	return (info != NULL) ? info->get_name(info->type_data) : NULL;
 }
 
+obs_module_t *obs_output_get_module(const char *id)
+{
+	obs_module_t *module = obs->first_module;
+	while (module) {
+		for (size_t i = 0; i < module->outputs.num; i++) {
+			if (strcmp(module->outputs.array[i], id) == 0) {
+				return module;
+			}
+		}
+		module = module->next;
+	}
+
+	module = obs->first_disabled_module;
+	while (module) {
+		for (size_t i = 0; i < module->outputs.num; i++) {
+			if (strcmp(module->outputs.array[i], id) == 0) {
+				return module;
+			}
+		}
+		module = module->next;
+	}
+
+	return NULL;
+}
+
+enum obs_module_load_state obs_output_load_state(const char *id)
+{
+	obs_module_t *module = obs_output_get_module(id);
+	if (!module) {
+		return OBS_MODULE_MISSING;
+	}
+	return module->load_state;
+}
+
 static const char *output_signals[] = {
 	"void start(ptr output)",
 	"void stop(ptr output, int code)",

+ 3 - 0
libobs/obs-output.h

@@ -85,6 +85,9 @@ struct obs_output_info {
 
 	/* required if OBS_OUTPUT_SERVICE */
 	const char *protocols;
+
+	/* Pointer to module that generated this output */
+	obs_module_t *module;
 };
 
 EXPORT void obs_register_output_s(const struct obs_output_info *info, size_t size);

+ 34 - 0
libobs/obs-service.c

@@ -35,6 +35,40 @@ const char *obs_service_get_display_name(const char *id)
 	return (info != NULL) ? info->get_name(info->type_data) : NULL;
 }
 
+obs_module_t *obs_service_get_module(const char *id)
+{
+	obs_module_t *module = obs->first_module;
+	while (module) {
+		for (size_t i = 0; i < module->services.num; i++) {
+			if (strcmp(module->services.array[i], id) == 0) {
+				return module;
+			}
+		}
+		module = module->next;
+	}
+
+	module = obs->first_disabled_module;
+	while (module) {
+		for (size_t i = 0; i < module->services.num; i++) {
+			if (strcmp(module->services.array[i], id) == 0) {
+				return module;
+			}
+		}
+		module = module->next;
+	}
+
+	return NULL;
+}
+
+enum obs_module_load_state obs_service_load_state(const char *id)
+{
+	obs_module_t *module = obs_service_get_module(id);
+	if (!module) {
+		return OBS_MODULE_MISSING;
+	}
+	return module->load_state;
+}
+
 static obs_service_t *obs_service_create_internal(const char *id, const char *name, obs_data_t *settings,
 						  obs_data_t *hotkey_data, bool private)
 {

+ 3 - 0
libobs/obs-service.h

@@ -104,6 +104,9 @@ struct obs_service_info {
 	const char *(*get_connect_info)(void *data, uint32_t type);
 
 	bool (*can_try_to_connect)(void *data);
+
+	/* Pointer to module that generated this service */
+	obs_module_t *module;
 };
 
 EXPORT void obs_register_service_s(const struct obs_service_info *info, size_t size);

+ 34 - 0
libobs/obs-source.c

@@ -130,6 +130,40 @@ const char *obs_source_get_display_name(const char *id)
 	return (info != NULL) ? info->get_name(info->type_data) : NULL;
 }
 
+obs_module_t *obs_source_get_module(const char *id)
+{
+	obs_module_t *module = obs->first_module;
+	while (module) {
+		for (size_t i = 0; i < module->sources.num; i++) {
+			if (strcmp(module->sources.array[i], id) == 0) {
+				return module;
+			}
+		}
+		module = module->next;
+	}
+
+	module = obs->first_disabled_module;
+	while (module) {
+		for (size_t i = 0; i < module->sources.num; i++) {
+			if (strcmp(module->sources.array[i], id) == 0) {
+				return module;
+			}
+		}
+		module = module->next;
+	}
+
+	return NULL;
+}
+
+enum obs_module_load_state obs_source_load_state(const char *id)
+{
+	obs_module_t *module = obs_source_get_module(id);
+	if (!module) {
+		return OBS_MODULE_MISSING;
+	}
+	return module->load_state;
+}
+
 static void allocate_audio_output_buffer(struct obs_source *source)
 {
 	size_t size = sizeof(float) * AUDIO_OUTPUT_FRAMES * MAX_AUDIO_CHANNELS * MAX_AUDIO_MIXES;

+ 3 - 0
libobs/obs-source.h

@@ -551,6 +551,9 @@ struct obs_source_info {
 	 * @param  source  Source that the filter is being added to
 	 */
 	void (*filter_add)(void *data, obs_source_t *source);
+
+	/** Pointer to module that generated this source **/
+	obs_module_t *module;
 };
 
 EXPORT void obs_register_source_s(const struct obs_source_info *info, size_t size);

+ 22 - 2
libobs/obs.c

@@ -1401,6 +1401,14 @@ void obs_shutdown(void)
 	}
 	obs->first_module = NULL;
 
+	module = obs->first_disabled_module;
+	while (module) {
+		struct obs_module *next = module->next;
+		free_module(module);
+		module = next;
+	}
+	obs->first_disabled_module = NULL;
+
 	obs_free_data();
 	obs_free_audio();
 	obs_free_video();
@@ -1412,14 +1420,26 @@ void obs_shutdown(void)
 	obs->procs = NULL;
 	obs->signals = NULL;
 
-	for (size_t i = 0; i < obs->module_paths.num; i++)
+	for (size_t i = 0; i < obs->module_paths.num; i++) {
 		free_module_path(obs->module_paths.array + i);
+	}
 	da_free(obs->module_paths);
 
-	for (size_t i = 0; i < obs->safe_modules.num; i++)
+	for (size_t i = 0; i < obs->safe_modules.num; i++) {
 		bfree(obs->safe_modules.array[i]);
+	}
 	da_free(obs->safe_modules);
 
+	for (size_t i = 0; i < obs->disabled_modules.num; i++) {
+		bfree(obs->disabled_modules.array[i]);
+	}
+	da_free(obs->disabled_modules);
+
+	for (size_t i = 0; i < obs->core_modules.num; i++) {
+		bfree(obs->core_modules.array[i]);
+	}
+	da_free(obs->core_modules);
+
 	if (obs->name_store_owned)
 		profiler_name_store_free(obs->name_store);
 

+ 79 - 0
libobs/obs.h

@@ -49,6 +49,7 @@ struct obs_encoder;
 struct obs_encoder_group;
 struct obs_service;
 struct obs_module;
+struct obs_module_metadata;
 struct obs_fader;
 struct obs_volmeter;
 struct obs_canvas;
@@ -64,6 +65,7 @@ typedef struct obs_encoder obs_encoder_t;
 typedef struct obs_encoder_group obs_encoder_group_t;
 typedef struct obs_service obs_service_t;
 typedef struct obs_module obs_module_t;
+typedef struct obs_module_metadata obs_module_metadata_t;
 typedef struct obs_fader obs_fader_t;
 typedef struct obs_volmeter obs_volmeter_t;
 typedef struct obs_canvas obs_canvas_t;
@@ -158,6 +160,18 @@ enum obs_bounds_type {
 	OBS_BOUNDS_MAX_ONLY,        /**< no scaling, maximum size only */
 };
 
+/**
+ * Used by libobs to define the state of a plugin/module.
+ */
+enum obs_module_load_state {
+	OBS_MODULE_INVALID,
+	OBS_MODULE_MISSING,
+	OBS_MODULE_ENABLED,
+	OBS_MODULE_DISABLED,
+	OBS_MODULE_DISABLED_SAFE,
+	OBS_MODULE_ERROR
+};
+
 struct obs_transform_info {
 	struct vec2 pos;
 	float rot;
@@ -474,6 +488,9 @@ EXPORT bool obs_get_audio_info2(struct obs_audio_info2 *oai2);
  */
 EXPORT int obs_open_module(obs_module_t **module, const char *path, const char *data_path);
 
+EXPORT bool obs_create_disabled_module(obs_module_t **module, const char *path, const char *data_path,
+				       enum obs_module_load_state state);
+
 /**
  * Initializes the module, which calls its obs_module_load export.  If the
  * module is already loaded, then this function does nothing and returns
@@ -484,6 +501,9 @@ EXPORT bool obs_init_module(obs_module_t *module);
 /** Returns a module based upon its name, or NULL if not found */
 EXPORT obs_module_t *obs_get_module(const char *name);
 
+/** Returns a module if it is disabled, or NULL if not found in the disabled list */
+EXPORT obs_module_t *obs_get_disabled_module(const char *name);
+
 /** Gets library of module */
 EXPORT void *obs_get_module_lib(obs_module_t *module);
 
@@ -514,6 +534,18 @@ EXPORT const char *obs_get_module_binary_path(obs_module_t *module);
 /** Returns the module data path */
 EXPORT const char *obs_get_module_data_path(obs_module_t *module);
 
+/** Adds a source type id to the module provided sources list */
+EXPORT void obs_module_add_source(obs_module_t *module, const char *id);
+
+/** Adds an output type id to the module provided outputs list */
+EXPORT void obs_module_add_output(obs_module_t *module, const char *id);
+
+/** Adds an encoder type id to the module provided encoders list */
+EXPORT void obs_module_add_encoder(obs_module_t *module, const char *id);
+
+/** Adds an encoder service id to the module provided services list */
+EXPORT void obs_module_add_service(obs_module_t *module, const char *id);
+
 #ifndef SWIG
 /**
  * Adds a module search path to be used with obs_find_modules.  If the search
@@ -533,6 +565,14 @@ EXPORT void obs_add_module_path(const char *bin, const char *data);
  */
 EXPORT void obs_add_safe_module(const char *name);
 
+/**
+ * Adds a module to the list of core modules (which cannot be disabled).
+ * If the list is empty, all modules are allowed.
+ *
+ * @param  name  Specifies the module's name (filename sans extension).
+ */
+EXPORT void obs_add_core_module(const char *name);
+
 /** Automatically loads all modules from module paths (convenience function) */
 EXPORT void obs_load_all_modules(void);
 
@@ -591,6 +631,21 @@ EXPORT lookup_t *obs_module_load_locale(obs_module_t *module, const char *defaul
  */
 EXPORT char *obs_find_module_file(obs_module_t *module, const char *file);
 
+/**
+ * Adds a module name to the disabled modules list.
+ *
+ * @param  name    The name of the module to disable
+ */
+EXPORT void obs_add_disabled_module(const char *name);
+
+/**
+ * Returns if a module can be disabled.
+ *
+ * @param  name    The name of the module to check
+ * @return         Boolean to indicate if module can be disabled
+ */
+EXPORT bool obs_get_module_allow_disable(const char *name);
+
 /**
  * Returns the path of a plugin module config file (whether it exists or not)
  *
@@ -975,6 +1030,12 @@ EXPORT void obs_display_size(obs_display_t *display, uint32_t *width, uint32_t *
 /** Returns the translated display name of a source */
 EXPORT const char *obs_source_get_display_name(const char *id);
 
+/** Returns a pointer to the module which provides the source */
+EXPORT obs_module_t *obs_source_get_module(const char *id);
+
+/** Returns the load state of a source's module given the id */
+EXPORT enum obs_module_load_state obs_source_load_state(const char *id);
+
 /**
  * Creates a source of the specified type with the specified settings.
  *
@@ -1809,6 +1870,12 @@ EXPORT void obs_scene_prune_sources(obs_scene_t *scene);
 
 EXPORT const char *obs_output_get_display_name(const char *id);
 
+/** Returns a pointer to the module which provides the output */
+EXPORT obs_module_t *obs_output_get_module(const char *id);
+
+/** Returns the load state of a output's module given the id */
+EXPORT enum obs_module_load_state obs_output_load_state(const char *id);
+
 /**
  * Creates an output.
  *
@@ -2141,6 +2208,12 @@ EXPORT uint64_t obs_output_get_pause_offset(obs_output_t *output);
 
 EXPORT const char *obs_encoder_get_display_name(const char *id);
 
+/** Returns a pointer to the module which provides the encoder */
+EXPORT obs_module_t *obs_encoder_get_module(const char *id);
+
+/** Returns the load state of an encoder's module given the id */
+EXPORT enum obs_module_load_state obs_encoder_load_state(const char *id);
+
 /**
  * Creates a video encoder context
  *
@@ -2387,6 +2460,12 @@ EXPORT void obs_encoder_group_destroy(obs_encoder_group_t *group);
 
 EXPORT const char *obs_service_get_display_name(const char *id);
 
+/** Returns a pointer to the module which provides the service */
+EXPORT obs_module_t *obs_service_get_module(const char *id);
+
+/** Returns the load state of a service's module given the id */
+EXPORT enum obs_module_load_state obs_service_load_state(const char *id);
+
 EXPORT obs_service_t *obs_service_create(const char *id, const char *name, obs_data_t *settings,
 					 obs_data_t *hotkey_data);