Browse Source

(API Change) Refactor module handling

Changed API:
- char *obs_find_plugin_file(const char *sub_path);

  Changed to: char *obs_module_file(const char *file);

  Cahnge it so you no longer need to specify a sub-path such as:
  obs_find_plugin_file("module_name/file.ext")

  Instead, now automatically handle the module data path so all you need
  to do is:
  obs_module_file("file.ext")

- int obs_load_module(const char *name);

  Changed to: int obs_open_module(obs_module_t *module,
                                  const char *path,
                                  const char *data_path);
              bool obs_init_module(obs_module_t module);

  Change the module loading API so that if the front-end chooses, it can
  load modules directly from a specified path, and associate a data
  directory with it on the spot.

  The module will not be initialized immediately; obs_init_module must
  be called on the module pointer in order to fully initialize the
  module.  This is done so a module can be disabled by the front-end if
  the it so chooses.

New API:
- void obs_add_module_path(const char *bin, const char *data);

  These functions allow you to specify new module search paths to add,
  and allow you to search through them, or optionally just load all
  modules from them.  If the string %module% is included, it will
  replace it with the module's name when that string is used as a
  lookup.  Data paths are now directly added to the module's internal
  storage structure, and when obs_find_module_file is used, it will look
  up the pointer to the obs_module structure and get its data directory
  that way.

  Example:
  obs_add_module_path("/opt/obs/my-modules/%module%/bin",
                      "/opt/obs/my-modules/%module%/data");

  This would cause it to additionally look for the binary of a
  hypthetical module named "foo" at /opt/obs/my-modules/foo/bin/foo.so
  (or libfoo.so), and then look for the data in
  /opt/obs/my-modules/foo/data.

  This gives the front-end more flexibility for handling third-party
  plugin modules, or handling all plugin modules in a custom way.

- void obs_find_modules(obs_find_module_callback_t callback, void
                        *param);

  This searches the existing paths for modules and calls the callback
  function when any are found.  Useful for plugin management and custom
  handling of the paths by the front-end if desired.

- void obs_load_all_modules(void);

  Search through the paths and both loads and initializes all modules
  automatically without custom handling.

- void obs_enum_modules(obs_enum_module_callback_t callback,
                        void *param);

  Enumerates currently opened modules.
jp9000 11 năm trước cách đây
mục cha
commit
59ea3becf2

+ 13 - 26
libobs/obs-cocoa.c

@@ -31,28 +31,23 @@ const char *get_module_extension(void)
 	return ".so";
 }
 
-// support both foo.so and libfoo.so for now
-static const char *plugin_patterns[] = {
-	OBS_INSTALL_PREFIX "obs-plugins/%s.so",
-	OBS_INSTALL_PREFIX "obs-plugins/lib%s.so",
-	"../obs-plugins/%s.so",
-	"../obs-plugins/lib%s.so"
+static const char *module_bin[] = {
+	"../obs-plugins",
+	OBS_INSTALL_PREFIX "obs-plugins",
 };
 
-static const int plugin_patterns_size =
-	sizeof(plugin_patterns)/sizeof(plugin_patterns[0]);
+static const char *module_data[] = {
+	"../data/obs-plugins/%module%",
+	OBS_INSTALL_DATA_PATH "obs-plugins/%module%",
+};
 
-char *find_plugin(const char *plugin)
-{
-	struct dstr path;
-	dstr_init(&path);
-	for(int i = 0; i < plugin_patterns_size; i++) {
-		dstr_printf(&path, plugin_patterns[i], plugin);
-		if(!access(path.array, F_OK))
-			break;
-	}
+static const int module_patterns_size =
+	sizeof(module_bin)/sizeof(module_bin[0]);
 
-	return path.array;
+void add_default_module_paths(void)
+{
+	for (int i = 0; i < module_patterns_size; i++)
+		obs_add_module_path(module_bin[i], module_data[i]);
 }
 
 char *find_libobs_data_file(const char *file)
@@ -63,14 +58,6 @@ char *find_libobs_data_file(const char *file)
 	return path.array;
 }
 
-char *obs_find_plugin_file(const char *file)
-{
-	struct dstr path;
-	dstr_init_copy(&path, OBS_INSTALL_DATA_PATH "/obs-plugins/");
-	dstr_cat(&path, file);
-	return path.array;
-}
-
 static void log_processor_name(void)
 {
 	char   *name = NULL;

+ 42 - 3
libobs/obs-internal.h

@@ -22,6 +22,7 @@
 #include "util/circlebuf.h"
 #include "util/dstr.h"
 #include "util/threading.h"
+#include "util/platform.h"
 #include "callback/signal.h"
 #include "callback/proc.h"
 
@@ -50,13 +51,49 @@ struct draw_callback {
 /* modules */
 
 struct obs_module {
-	char *name;
+	const char *file;
+	char *bin_path;
+	char *data_path;
 	void *module;
-	void (*set_locale)(const char *locale);
+	bool loaded;
+
+	bool        (*load)(uint32_t libobs_ver);
+	void        (*unload)(void);
+	void        (*set_locale)(const char *locale);
+	void        (*free_locale)(void);
+	uint32_t    (*ver)(void);
+	void        (*set_pointer)(obs_module_t module);
+	const char *(*name)(void);
+	const char *(*description)(void);
+	const char *(*author)(void);
+
+	struct obs_module *next;
 };
 
 extern void free_module(struct obs_module *mod);
 
+struct obs_module_path {
+	char *bin;
+	char *data;
+};
+
+static inline void free_module_path(struct obs_module_path *omp)
+{
+	if (omp) {
+		bfree(omp->bin);
+		bfree(omp->data);
+	}
+}
+
+static inline bool check_path(const char *data, const char *path,
+		struct dstr *output)
+{
+	dstr_copy(output, path);
+	dstr_cat(output, data);
+
+	return os_file_exists(output->array);
+}
+
 
 /* ------------------------------------------------------------------------- */
 /* views */
@@ -161,7 +198,9 @@ struct obs_core_data {
 };
 
 struct obs_core {
-	DARRAY(struct obs_module)       modules;
+	struct obs_module               *first_module;
+	DARRAY(struct obs_module_path)  module_paths;
+
 	DARRAY(struct obs_source_info)  input_types;
 	DARRAY(struct obs_source_info)  filter_types;
 	DARRAY(struct obs_source_info)  transition_types;

+ 277 - 43
libobs/obs-module.c

@@ -22,7 +22,6 @@
 #include "obs-internal.h"
 #include "obs-module.h"
 
-extern char *find_plugin(const char *plugin);
 extern const char *get_module_extension(void);
 
 static inline int req_func_not_found(const char *name, const char *path)
@@ -33,76 +32,313 @@ static inline int req_func_not_found(const char *name, const char *path)
 	return MODULE_MISSING_EXPORTS;
 }
 
-#define LOAD_REQ_SIZE_FUNC(func, module, path)         \
-	func = os_dlsym(module, #func);                \
-	if (!func)                                     \
-		return req_func_not_found(#func, path)
-
-static int call_module_load(void *module, const char *path)
+static int load_module_exports(struct obs_module *mod, const char *path)
 {
-	bool (*obs_module_load)(uint32_t obs_ver) = NULL;
-
-	obs_module_load = os_dlsym(module, "obs_module_load");
-	if (!obs_module_load)
+	mod->load = os_dlsym(mod->module, "obs_module_load");
+	if (!mod->load)
 		return req_func_not_found("obs_module_load", path);
 
-	if (!obs_module_load(LIBOBS_API_VER)) {
-		blog(LOG_ERROR, "Module '%s' failed to load: "
-		                "obs_module_load failed", path);
-		return MODULE_ERROR;
-	}
-
+	mod->set_pointer = os_dlsym(mod->module, "obs_module_set_pointer");
+	if (!mod->set_pointer)
+		return req_func_not_found("obs_module_set_pointer", path);
+
+	mod->ver = os_dlsym(mod->module, "obs_module_ver");
+	if (!mod->ver)
+		return req_func_not_found("obs_module_ver", path);
+
+	/* optional exports */
+	mod->unload      = os_dlsym(mod->module, "obs_module_unload");
+	mod->set_locale  = os_dlsym(mod->module, "obs_module_set_locale");
+	mod->free_locale = os_dlsym(mod->module, "obs_module_free_locale");
+	mod->name        = os_dlsym(mod->module, "obs_module_name");
+	mod->description = os_dlsym(mod->module, "obs_module_description");
+	mod->author      = os_dlsym(mod->module, "obs_module_author");
 	return MODULE_SUCCESS;
 }
 
-int obs_load_module(const char *path)
+int obs_open_module(obs_module_t *module, const char *path,
+		const char *data_path)
 {
-	struct obs_module mod;
-	char *plugin_path = find_plugin(path);
+	struct obs_module mod = {0};
 	int errorcode;
 
-	mod.module = os_dlopen(plugin_path);
-	bfree(plugin_path);
+	if (!module || !path || !obs)
+		return MODULE_ERROR;
+
+	mod.module = os_dlopen(path);
 	if (!mod.module) {
 		blog(LOG_WARNING, "Module '%s' not found", path);
 		return MODULE_FILE_NOT_FOUND;
 	}
 
-	errorcode = call_module_load(mod.module, path);
-	if (errorcode != MODULE_SUCCESS) {
-		os_dlclose(mod.module);
+	errorcode = load_module_exports(&mod, path);
+	if (errorcode != MODULE_SUCCESS)
 		return errorcode;
-	}
 
-	mod.name       = bstrdup(path);
-	mod.set_locale = os_dlsym(mod.module, "obs_module_set_locale");
+	mod.bin_path  = bstrdup(path);
+	mod.file      = strrchr(mod.bin_path, '/');
+	mod.file      = (!mod.file) ? mod.bin_path : (mod.file + 1);
+	mod.data_path = bstrdup(data_path);
+	mod.next      = obs->first_module;
+
+	*module = bmemdup(&mod, sizeof(mod));
+	obs->first_module = (*module);
+	mod.set_pointer(*module);
 
 	if (mod.set_locale)
 		mod.set_locale(obs->locale);
 
-	da_push_back(obs->modules, &mod);
 	return MODULE_SUCCESS;
 }
 
+bool obs_init_module(obs_module_t module)
+{
+	if (!module || !obs)
+		return false;
+	if (module->loaded)
+		return true;
+
+	module->loaded = module->load(LIBOBS_API_VER);
+	if (!module->loaded)
+		blog(LOG_WARNING, "Failed to initialize module '%s'",
+				module->file);
+
+	return module->loaded;
+}
+
+char *obs_find_module_file(obs_module_t module, const char *file)
+{
+	struct dstr output = {0};
+
+	if (!module)
+		return NULL;
+
+	dstr_copy(&output, module->data_path);
+	if (!dstr_isempty(&output) && dstr_end(&output) != '/')
+		dstr_cat_ch(&output, '/');
+	dstr_cat(&output, file);
+
+	if (!os_file_exists(output.array))
+		dstr_free(&output);
+	return output.array;
+}
+
+void obs_add_module_path(const char *bin, const char *data)
+{
+	struct obs_module_path omp;
+
+	if (!obs || !bin || !data) return;
+
+	omp.bin  = bstrdup(bin);
+	omp.data = bstrdup(data);
+	da_push_back(obs->module_paths, &omp);
+}
+
+static void load_all_callback(void *param, const struct obs_module_info *info)
+{
+	obs_module_t module;
+
+	int code = obs_open_module(&module, info->bin_path, info->data_path);
+	if (code != MODULE_SUCCESS) {
+		blog(LOG_DEBUG, "Failed to load module file '%s': %d",
+				info->bin_path, code);
+		return;
+	}
+
+	obs_init_module(module);
+
+	UNUSED_PARAMETER(param);
+}
+
+void obs_load_all_modules(void)
+{
+	obs_find_modules(load_all_callback, NULL);
+}
+
+static inline void make_data_dir(struct dstr *parsed_data_dir,
+		const char *data_dir, const char *name)
+{
+	dstr_copy(parsed_data_dir, data_dir);
+	dstr_replace(parsed_data_dir, "%module%", name);
+	if (dstr_end(parsed_data_dir) == '/')
+		dstr_resize(parsed_data_dir, parsed_data_dir->len - 1);
+}
+
+static char *make_data_directory(const char *module_name, const char *data_dir)
+{
+	struct dstr parsed_data_dir = {0};
+	bool found = false;
+
+	make_data_dir(&parsed_data_dir, data_dir, module_name);
+
+	found = os_file_exists(parsed_data_dir.array);
+
+	if (!found && astrcmpi_n(module_name, "lib", 3) == 0) {
+		make_data_dir(&parsed_data_dir, data_dir, module_name + 3);
+		found = os_file_exists(parsed_data_dir.array);
+	}
+
+	if (!found)
+		dstr_free(&parsed_data_dir);
+
+	return parsed_data_dir.array;
+}
+
+static bool parse_binary_from_directory(struct dstr *parsed_bin_path,
+		const char *bin_path, const char *file)
+{
+	struct dstr directory = {0};
+	bool found = true;
+
+	dstr_copy(&directory, bin_path);
+	dstr_replace(&directory, "%module%", file);
+	if (dstr_end(&directory) != '/')
+		dstr_cat_ch(&directory, '/');
+
+	dstr_copy_dstr(parsed_bin_path, &directory);
+	dstr_cat(parsed_bin_path, file);
+	dstr_cat(parsed_bin_path, get_module_extension());
+
+	if (!os_file_exists(parsed_bin_path->array)) {
+		/* if the file doesn't exist, check with 'lib' prefix */
+		dstr_copy_dstr(parsed_bin_path, &directory);
+		dstr_cat(parsed_bin_path, "lib");
+		dstr_cat(parsed_bin_path, file);
+		dstr_cat(parsed_bin_path, get_module_extension());
+
+		/* if neither exist, don't include this as a library */
+		if (!os_file_exists(parsed_bin_path->array)) {
+			dstr_free(parsed_bin_path);
+			found = false;
+		}
+	}
+
+	dstr_free(&directory);
+	return found;
+}
+
+static void process_found_module(struct obs_module_path *omp,
+		const char *path, bool directory,
+		obs_find_module_callback_t callback, void *param)
+{
+	struct obs_module_info info;
+	struct dstr            name = {0};
+	struct dstr            parsed_bin_path = {0};
+	const char             *file;
+	char                   *parsed_data_dir;
+	bool                   bin_found = true;
+
+	file = strrchr(path, '/');
+	file = file ? (file + 1) : path;
+
+	dstr_copy(&name, file);
+	if (!directory) {
+		char *ext = strrchr(name.array, '.');
+		if (ext)
+			dstr_resize(&name, ext - name.array);
+
+		dstr_copy(&parsed_bin_path, path);
+	} else {
+		bin_found = parse_binary_from_directory(&parsed_bin_path,
+				omp->bin, file);
+	}
+
+	parsed_data_dir = make_data_directory(name.array, omp->data);
+
+	if (parsed_data_dir && bin_found) {
+		info.bin_path  = parsed_bin_path.array;
+		info.data_path = parsed_data_dir;
+		callback(param, &info);
+	}
+
+	bfree(parsed_data_dir);
+	dstr_free(&name);
+	dstr_free(&parsed_bin_path);
+}
+
+static void find_modules_in_path(struct obs_module_path *omp,
+		obs_find_module_callback_t callback, void *param)
+{
+	struct dstr search_path = {0};
+	char *module_start;
+	bool search_directories = false;
+	os_glob_t gi;
+
+	dstr_copy(&search_path, omp->bin);
+
+	module_start = strstr(search_path.array, "%module%");
+	if (module_start) {
+		dstr_resize(&search_path, module_start - search_path.array);
+		search_directories = true;
+	}
+
+	if (!dstr_isempty(&search_path) && dstr_end(&search_path) != '/')
+		dstr_cat_ch(&search_path, '/');
+
+	dstr_cat_ch(&search_path, '*');
+	if (!search_directories)
+		dstr_cat(&search_path, get_module_extension());
+
+	if (os_glob(search_path.array, 0, &gi) == 0) {
+		for (size_t i = 0; i < gi->gl_pathc; i++) {
+			if (search_directories == gi->gl_pathv[i].directory)
+				process_found_module(omp,
+						gi->gl_pathv[i].path,
+						search_directories,
+						callback, param);
+		}
+
+		os_globfree(gi);
+	}
+
+	dstr_free(&search_path);
+}
+
+void obs_find_modules(obs_find_module_callback_t callback, void *param)
+{
+	if (!obs)
+		return;
+
+	for (size_t i = 0; i < obs->module_paths.num; i++) {
+		struct obs_module_path *omp = obs->module_paths.array + i;
+		find_modules_in_path(omp, callback, param);
+	}
+}
+
+void obs_enum_modules(obs_enum_module_callback_t callback, void *param)
+{
+	struct obs_module *module;
+	if (!obs)
+		return;
+
+	module = obs->first_module;
+	while (module) {
+		callback(param, module);
+		module = module->next;
+	}
+}
+
 void free_module(struct obs_module *mod)
 {
 	if (!mod)
 		return;
 
 	if (mod->module) {
-		void (*module_unload)(void);
+		if (mod->free_locale)
+			mod->free_locale();
 
-		module_unload = os_dlsym(mod->module, "obs_module_unload");
-		if (module_unload)
-			module_unload();
+		if (mod->loaded && mod->unload)
+			mod->unload();
 
 		os_dlclose(mod->module);
 	}
 
-	bfree(mod->name);
+	bfree(mod->bin_path);
+	bfree(mod->data_path);
+	bfree(mod);
 }
 
-lookup_t obs_module_load_locale(const char *module, const char *default_locale,
+lookup_t obs_module_load_locale(obs_module_t module, const char *default_locale,
 		const char *locale)
 {
 	struct dstr str    = {0};
@@ -113,12 +349,11 @@ lookup_t obs_module_load_locale(const char *module, const char *default_locale,
 		return NULL;
 	}
 
-	dstr_copy(&str, module);
-	dstr_cat(&str, "/locale/");
+	dstr_copy(&str, "locale/");
 	dstr_cat(&str, default_locale);
 	dstr_cat(&str, ".ini");
 
-	char *file = obs_find_plugin_file(str.array);
+	char *file = obs_find_module_file(module, str.array);
 	if (file)
 		lookup = text_lookup_create(file);
 
@@ -126,23 +361,22 @@ lookup_t obs_module_load_locale(const char *module, const char *default_locale,
 
 	if (!lookup) {
 		blog(LOG_WARNING, "Failed to load '%s' text for module: '%s'",
-				default_locale, module);
+				default_locale, module->file);
 		goto cleanup;
 	}
 
 	if (astrcmpi(locale, default_locale) == 0)
 		goto cleanup;
 
-	dstr_copy(&str, module);
-	dstr_cat(&str, "/locale/");
+	dstr_copy(&str, "/locale/");
 	dstr_cat(&str, locale);
 	dstr_cat(&str, ".ini");
 
-	file = obs_find_plugin_file(str.array);
+	file = obs_find_module_file(module, str.array);
 
 	if (!text_lookup_add(lookup, file))
 		blog(LOG_WARNING, "Failed to load '%s' text for module: '%s'",
-				locale, module);
+				locale, module->file);
 
 	bfree(file);
 cleanup:

+ 28 - 8
libobs/obs-module.h

@@ -32,8 +32,15 @@
  */
 
 /** Required: Declares a libobs module. */
-#define OBS_DECLARE_MODULE()                                          \
-	MODULE_EXPORT uint32_t obs_module_ver(void);                  \
+#define OBS_DECLARE_MODULE() \
+	static obs_module_t obs_module_pointer; \
+	MODULE_EXPORT void obs_module_set_pointer(obs_module_t module); \
+	void obs_module_set_pointer(obs_module_t module) \
+	{ \
+		obs_module_pointer = module; \
+	} \
+	obs_module_t obs_current_module(void) {return obs_module_pointer;} \
+	MODULE_EXPORT uint32_t obs_module_ver(void); \
 	uint32_t obs_module_ver(void) {return LIBOBS_API_VER;}
 
 /**
@@ -53,11 +60,10 @@ MODULE_EXPORT void obs_module_unload(void);
 /** Called to set the current locale data for the module.  */
 MODULE_EXPORT void obs_module_set_locale(const char *locale);
 
-/**
- * Optional: Use this macro in a module to use default locale handling.  Use
- * the OBS_MODULE_FREE_DEFAULT_LOCALE macro in obs_module_unload to free the
- * locale data when the module unloads.
- */
+/** Called to free the current locale data for the module.  */
+MODULE_EXPORT void obs_module_free_locale(void);
+
+/** Optional: Use this macro in a module to use default locale handling. */
 #define OBS_MODULE_USE_DEFAULT_LOCALE(module_name, default_locale) \
 	lookup_t obs_module_lookup = NULL; \
 	const char *obs_module_text(const char *val) \
@@ -69,7 +75,8 @@ MODULE_EXPORT void obs_module_set_locale(const char *locale);
 	void obs_module_set_locale(const char *locale) \
 	{ \
 		if (obs_module_lookup) text_lookup_destroy(obs_module_lookup); \
-		obs_module_lookup = obs_module_load_locale(module_name, \
+		obs_module_lookup = obs_module_load_locale( \
+				obs_current_module(), \
 				default_locale, locale); \
 	}
 
@@ -79,6 +86,16 @@ MODULE_EXPORT void obs_module_set_locale(const char *locale);
 /** Helper function for looking up locale if default locale handler was used */
 extern const char *obs_module_text(const char *lookup_string);
 
+/** Helper function that returns the current module */
+extern obs_module_t obs_current_module(void);
+
+/**
+ * Returns the location to a module data file associated with the current
+ * module.  Free with bfree when complete.  Equivalent to:
+ *    obs_find_module_file(obs_current_modile(), file);
+ */
+#define obs_module_file(file) obs_find_module_file(obs_current_module(), file)
+
 /**
  * Optional: Declares the author(s) of the module
  *
@@ -88,5 +105,8 @@ extern const char *obs_module_text(const char *lookup_string);
 	MODULE_EXPORT const char *obs_module_author(void); \
 	const char *obs_module_author(void) {return name;}
 
+/** Optional: Returns the full name of the module */
+MODULE_EXPORT const char *obs_module_name(void);
+
 /** Optional: Returns a description of the module */
 MODULE_EXPORT const char *obs_module_description(void);

+ 19 - 82
libobs/obs-nix.c

@@ -22,77 +22,36 @@
 #include <sys/sysinfo.h>
 #include <sys/utsname.h>
 #include "util/dstr.h"
-#include "obs.h"
+#include "obs-internal.h"
 
 const char *get_module_extension(void)
 {
 	return ".so";
 }
 
-static inline bool check_path(const char* data, const char *path,
-		struct dstr * output)
-{
-	dstr_copy(output, path);
-	dstr_cat(output, data);
-
-	blog(LOG_INFO, "Attempting path: %s\n", output->array);
-
-	return access(output->array, R_OK) == 0;
-}
-
-static inline bool check_lib_path(const char* data, const char *path,
-		struct dstr *output)
-{
-	bool result = false;
-	struct dstr tmp;
+#ifdef __LP64__
+#define BIT_STRING "64bit"
+#else
+#define BIT_STRING "32bit"
+#endif
 
-	dstr_init(&tmp);
-	dstr_cat(&tmp, data);
-	dstr_cat(&tmp, ".so");
-	result = check_path(tmp.array, path, output);
+static const char *module_bin[] = {
+	"../../obs-plugins/" BIT_STRING,
+	OBS_INSTALL_PREFIX "lib/obs-plugins",
+};
 
-	dstr_free(&tmp);
+static const char *module_data[] = {
+	OBS_DATA_PATH "/obs-plugins/%module%",
+	OBS_INSTALL_DATA_PATH "/obs-plugins/%module%",
+};
 
-	return result;
-}
+static const int module_patterns_size =
+	sizeof(module_bin)/sizeof(module_bin[0]);
 
-/*
- *   /usr/local/lib/obs-plugins
- *   /usr/lib/obs-plugins
- */
-char *find_plugin(const char *plugin)
+void add_default_module_paths(void)
 {
-	struct dstr output;
-	dstr_init(&output);
-
-	if(sizeof(void*) == 4) {
-		if (check_lib_path(plugin, "../../obs-plugins/32bit/", &output))
-			return output.array;
-
-		if (check_lib_path(plugin, "../../obs-plugins/32bit/lib", &output))
-			return output.array;
-	} else {
-		if (check_lib_path(plugin, "../../obs-plugins/64bit/", &output))
-			return output.array;
-
-		if (check_lib_path(plugin, "../../obs-plugins/64bit/lib", &output))
-			return output.array;
-	}
-
-	if (OBS_INSTALL_PREFIX [0] != 0) {
-		if (check_lib_path(plugin,
-					OBS_INSTALL_PREFIX "lib/obs-plugins/",
-					&output))
-			return output.array;
-
-		if (check_lib_path(plugin,
-					OBS_INSTALL_PREFIX "lib/obs-plugins/lib",
-					&output))
-			return output.array;
-	}
-
-	dstr_free(&output);
-	return NULL;
+	for (int i = 0; i < module_patterns_size; i++)
+		obs_add_module_path(module_bin[i], module_data[i]);
 }
 
 /*
@@ -117,28 +76,6 @@ char *find_libobs_data_file(const char *file)
 	return NULL;
 }
 
-/*
- *   /usr/local/share/obs-plugins
- *   /usr/share/obs-plugins
- */
-char *obs_find_plugin_file(const char *file)
-{
-	struct dstr output;
-	dstr_init(&output);
-
-	if (check_path(file, OBS_DATA_PATH "/obs-plugins/", &output))
-		return output.array;
-
-	if (OBS_INSTALL_PREFIX [0] != 0) {
-		if (check_path(file, OBS_INSTALL_DATA_PATH "/obs-plugins/",
-					&output))
-		return output.array;
-	}
-
-	dstr_free(&output);
-	return NULL;
-}
-
 static void log_processor_info(void)
 {
 	FILE *fp;

+ 19 - 60
libobs/obs-windows.c

@@ -27,57 +27,32 @@ const char *get_module_extension(void)
 	return ".dll";
 }
 
-static inline bool check_path(const char* data, const char *path,
-		struct dstr * output)
-{
-	dstr_copy(output, path);
-	dstr_cat(output, data);
-
-	blog(LOG_DEBUG, "Attempting path: %s\n", output->array);
-
-	return os_file_exists(output->array);
-}
-
-static inline bool check_lib_path(const char* data, const char *path,
-		struct dstr *output)
-{
-	bool result = false;
-	struct dstr tmp;
-
-	dstr_init_copy(&tmp, data);
-	dstr_cat(&tmp, ".dll");
-	result = check_path(tmp.array, path, output);
-
-	dstr_free(&tmp);
-
-	return result;
-}
-
-/* on windows, plugin files are located in [base directory]/plugins/[bit] */
-char *find_plugin(const char *plugin)
-{
-	struct dstr path;
-	dstr_init(&path);
-
 #ifdef _WIN64
-	if (check_lib_path(plugin, "obs-plugins/64bit/", &path))
+#define BIT_STRING "64bit"
 #else
-	if (check_lib_path(plugin, "obs-plugins/32bit/", &path))
+#define BIT_STRING "32bit"
 #endif
-		return path.array;
 
-#ifdef _WIN64
-	if (check_lib_path(plugin, "../../obs-plugins/64bit/", &path))
-#else
-	if (check_lib_path(plugin, "../../obs-plugins/32bit/", &path))
-#endif
-		return path.array;
+static const char *module_bin[] = {
+	"obs-plugins/" BIT_STRING,
+	"../../obs-plugins/" BIT_STRING,
+};
 
-	dstr_free(&path);
-	return NULL;
+static const char *module_data[] = {
+	"data/%module%",
+	"../../data/obs-plugins/%module%"
+};
+
+static const int module_patterns_size =
+	sizeof(module_bin)/sizeof(module_bin[0]);
+
+void add_default_module_paths(void)
+{
+	for (int i = 0; i < module_patterns_size; i++)
+		obs_add_module_path(module_bin[i], module_data[i]);
 }
 
-/* on windows, points to [base directory]/libobs */
+/* on windows, points to [base directory]/data/libobs */
 char *find_libobs_data_file(const char *file)
 {
 	struct dstr path;
@@ -93,22 +68,6 @@ char *find_libobs_data_file(const char *file)
 	return NULL;
 }
 
-/* on windows, data files should always be in [base directory]/data */
-char *obs_find_plugin_file(const char *file)
-{
-	struct dstr path;
-	dstr_init(&path);
-
-	if (check_path(file, "data/obs-plugins/", &path))
-		return path.array;
-
-	if (check_path(file, "../../data/obs-plugins/", &path))
-		return path.array;
-
-	dstr_free(&path);
-	return NULL;
-}
-
 static void log_processor_info(void)
 {
 	HKEY    key;

+ 20 - 6
libobs/obs.c

@@ -24,6 +24,7 @@
 
 struct obs_core *obs = NULL;
 
+extern void add_default_module_paths(void);
 extern char *find_libobs_data_file(const char *file);
 
 static inline void make_gs_init_data(struct gs_init_data *gid,
@@ -540,6 +541,7 @@ static bool obs_init(const char *locale)
 
 	obs->locale = bstrdup(locale);
 	obs_register_source(&scene_info);
+	add_default_module_paths();
 	return true;
 }
 
@@ -561,6 +563,8 @@ bool obs_startup(const char *locale)
 
 void obs_shutdown(void)
 {
+	struct obs_module *module;
+
 	if (!obs)
 		return;
 
@@ -582,9 +586,17 @@ void obs_shutdown(void)
 	proc_handler_destroy(obs->procs);
 	signal_handler_destroy(obs->signals);
 
-	for (size_t i = 0; i < obs->modules.num; i++)
-		free_module(obs->modules.array+i);
-	da_free(obs->modules);
+	module = obs->first_module;
+	while (module) {
+		struct obs_module *next = module->next;
+		free_module(module);
+		module = next;
+	}
+	obs->first_module = NULL;
+
+	for (size_t i = 0; i < obs->module_paths.num; i++)
+		free_module_path(obs->module_paths.array+i);
+	da_free(obs->module_paths);
 
 	bfree(obs->locale);
 	bfree(obs);
@@ -603,6 +615,7 @@ uint32_t obs_get_version(void)
 
 void obs_set_locale(const char *locale)
 {
+	struct obs_module *module;
 	if (!obs)
 		return;
 
@@ -610,11 +623,12 @@ void obs_set_locale(const char *locale)
 		bfree(obs->locale);
 	obs->locale = bstrdup(locale);
 
-	for (size_t i = 0; i < obs->modules.num; i++) {
-		struct obs_module *module = obs->modules.array+i;
-
+	module = obs->first_module;
+	while (module) {
 		if (module->set_locale)
 			module->set_locale(locale);
+
+		module = module->next;
 	}
 }
 

+ 72 - 14
libobs/obs.h

@@ -45,6 +45,7 @@ struct obs_scene_item;
 struct obs_output;
 struct obs_encoder;
 struct obs_service;
+struct obs_module;
 
 typedef struct obs_display    *obs_display_t;
 typedef struct obs_view       *obs_view_t;
@@ -54,6 +55,7 @@ typedef struct obs_scene_item *obs_sceneitem_t;
 typedef struct obs_output     *obs_output_t;
 typedef struct obs_encoder    *obs_encoder_t;
 typedef struct obs_service    *obs_service_t;
+typedef struct obs_module     *obs_module_t;
 
 #include "obs-source.h"
 #include "obs-encoder.h"
@@ -257,18 +259,82 @@ EXPORT bool obs_get_video_info(struct obs_video_info *ovi);
 EXPORT bool obs_get_audio_info(struct audio_output_info *ai);
 
 /**
- * Loads a plugin module
+ * Opens a plugin module directly from a specific path.
  *
- *   A plugin module contains exports for inputs/fitlers/transitions/outputs.
- * See obs-source.h and obs-output.h for more information on the exports to
- * use.
+ * If the module already exists then the function will return successful, and
+ * the module parameter will be given the pointer to the existing module.
+ *
+ * This does not initialize the module, it only loads the module image.  To
+ * initialize the module, call obs_init_module.
+ *
+ * @param  module     The pointer to the created module.
+ * @param  path       Specifies the path to the module library file.  If the
+ *                    extension is not specified, it will use the extension
+ *                    appropriate to the operating system.
+ * @param  data_path  Specifies the path to the directory where the module's
+ *                    data files are stored.
+ * @returns           MODULE_SUCCESS if successful
+ *                    MODULE_ERROR if a generic error occurred
+ *                    MODULE_FILE_NOT_FOUND if the module was not found
+ *                    MODULE_MISSING_EXPORTS if required exports are missing
+ *                    MODULE_INCOMPATIBLE_VER if incompatible version
  */
-EXPORT int obs_load_module(const char *path);
+EXPORT int obs_open_module(obs_module_t *module, const char *path,
+		const char *data_path);
+
+/**
+ * Initializes the module, which calls its obs_module_load export.  If the
+ * module is alrady loaded, then this function does nothing and returns
+ * successful.
+ */
+EXPORT bool obs_init_module(obs_module_t module);
+
+/**
+ * Adds a module search path to be used with obs_find_modules.  If the search
+ * path strings contain %module%, that text will be replaced with the module
+ * name when used.
+ *
+ * @param  bin   Specifies the module's binary directory search path.
+ * @param  data  Specifies the module's data directory search path.
+ */
+EXPORT void obs_add_module_path(const char *bin, const char *data);
+
+/** Automatically loads all modules from module paths (convenience function) */
+EXPORT void obs_load_all_modules(void);
+
+struct obs_module_info {
+	const char *bin_path;
+	const char *data_path;
+};
+
+typedef void (*obs_find_module_callback_t)(void *param,
+		const struct obs_module_info *info);
+
+/** Finds all modules within the search paths added by obs_add_module_path. */
+EXPORT void obs_find_modules(obs_find_module_callback_t callback, void *param);
+
+typedef void (*obs_enum_module_callback_t)(void *param, obs_module_t module);
+
+/** Enumerates all loaded modules */
+EXPORT void obs_enum_modules(obs_enum_module_callback_t callback, void *param);
 
 /** Helper function for using default module locale */
-EXPORT lookup_t obs_module_load_locale(const char *module,
+EXPORT lookup_t obs_module_load_locale(obs_module_t module,
 		const char *default_locale, const char *locale);
 
+/**
+ * Returns the location of a plugin module data file.
+ *
+ * @note   Modules should use obs_module_file function defined in obs-module.h
+ *         as a more elegant means of getting their files without having to
+ *         specify the module parameter.
+ *
+ * @param  module  The module associated with the file to locate
+ * @param  file    The file to locate
+ * @return         Path string, or NULL if not found.  Use bfree to free string.
+ */
+EXPORT char *obs_find_module_file(obs_module_t module, const char *file);
+
 /**
  * Enumerates all available inputs source types.
  *
@@ -368,14 +434,6 @@ EXPORT obs_encoder_t obs_get_encoder_by_name(const char *name);
 /** Gets an service by its name. */
 EXPORT obs_service_t obs_get_service_by_name(const char *name);
 
-/**
- * Returns the location of a plugin data file.
- *
- *   file: Name of file to locate.  For example, "myplugin/mydata.data"
- *   returns: Path string, or NULL if not found.  Use bfree to free string.
- */
-EXPORT char *obs_find_plugin_file(const char *file);
-
 /** Returns the default effect for generic RGB/YUV drawing */
 EXPORT effect_t obs_get_default_effect(void);
 

+ 1 - 22
obs/window-basic-main.cpp

@@ -534,28 +534,7 @@ void OBSBasic::OBSInit()
 
 	InitOBSCallbacks();
 
-	/* TODO: this is a test, all modules will be searched for and loaded
-	 * automatically later */
-	obs_load_module("image-source");
-	// obs_load_module("test-input");
-	obs_load_module("obs-ffmpeg");
-	obs_load_module("obs-libfdk");
-	obs_load_module("obs-x264");
-	obs_load_module("obs-outputs");
-	obs_load_module("rtmp-services");
-#ifdef __APPLE__
-	obs_load_module("mac-avcapture");
-	obs_load_module("mac-capture");
-#elif _WIN32
-	obs_load_module("win-wasapi");
-	obs_load_module("win-capture");
-	obs_load_module("win-dshow");
-#else
-	obs_load_module("linux-xshm");
-	obs_load_module("linux-xcomposite");
-	obs_load_module("linux-pulseaudio");
-	obs_load_module("linux-v4l2");
-#endif
+	obs_load_all_modules();
 
 	if (!InitOutputs())
 		throw "Failed to initialize outputs";

+ 1 - 2
plugins/mac-capture/mac-display-capture.m

@@ -181,8 +181,7 @@ static void *display_capture_create(obs_data_t settings,
 	if (!dc->sampler)
 		goto fail;
 
-	char *effect_file = obs_find_plugin_file(
-			"mac-capture/draw_rect.effect");
+	char *effect_file = obs_module_file("draw_rect.effect");
 	dc->draw_effect = gs_create_effect_from_file(effect_file, NULL);
 	bfree(effect_file);
 	if (!dc->draw_effect)

+ 2 - 2
plugins/rtmp-services/rtmp-common.c

@@ -207,7 +207,7 @@ static obs_properties_t rtmp_common_properties(void)
 			obs_module_text("Service"),
 			OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
 
-	file = obs_find_plugin_file("rtmp-services/services.json");
+	file = obs_module_file("services.json");
 	if (file) {
 		json_t *root = build_service_list(list, file);
 		obs_properties_set_param(ppts, root, properties_data_destroy);
@@ -305,7 +305,7 @@ static bool rtmp_common_initialize(void *data, obs_output_t output)
 	struct rtmp_common *service = data;
 	char               *file;
 
-	file = obs_find_plugin_file("rtmp-services/services.json");
+	file = obs_module_file("services.json");
 	if (file) {
 		json_t *root = open_json_file(file);
 		if (root) {

+ 1 - 1
plugins/win-capture/dc-capture.c

@@ -216,7 +216,7 @@ effect_t create_opaque_effect(void)
 	char *effect_file;
 	char *error_string = NULL;
 
-	effect_file = obs_find_plugin_file("win-capture/opaque.effect");
+	effect_file = obs_module_file("opaque.effect");
 	if (!effect_file) {
 		blog(LOG_ERROR, "[create_opaque_effect] Could not find "
 		                "opaque effect file");

+ 2 - 3
test/osx/test.mm

@@ -62,9 +62,8 @@ static void CreateOBS(NSView *view)
 static SceneContext SetupScene()
 {
 	/* ------------------------------------------------------ */
-	/* load module */
-	if (obs_load_module("test-input") != 0)
-		throw "Couldn't load module";
+	/* load modules */
+	obs_load_all_modules();
 
 	/* ------------------------------------------------------ */
 	/* create source */

+ 2 - 2
test/test-input/test-filter.c

@@ -1,4 +1,4 @@
-#include <obs.h>
+#include <obs-module.h>
 
 struct test_filter {
 	obs_source_t source;
@@ -31,7 +31,7 @@ static void *filter_create(obs_data_t settings, obs_source_t source)
 
 	gs_entercontext(obs_graphics());
 
-	effect_file = obs_find_plugin_file("test-input/test.effect");
+	effect_file = obs_module_file("test.effect");
 
 	tf->source = source;
 	tf->whatever = gs_create_effect_from_file(effect_file, NULL);

+ 2 - 3
test/win/test.cpp

@@ -152,9 +152,8 @@ int WINAPI WinMain(HINSTANCE instance, HINSTANCE prevInstance, LPSTR cmdLine,
 		CreateOBS(hwnd);
 
 		/* ------------------------------------------------------ */
-		/* load module */
-		if (obs_load_module("test-input") != 0)
-			throw "Couldn't load module";
+		/* load modules */
+		obs_load_all_modules();
 
 		/* ------------------------------------------------------ */
 		/* create source */