frontend: Update app module loading behavior

With this change module loading in the application is managed by the
plugin manager and allows the application to explicitly load core
modules independantly from 3rd party (plugin) modules.

The implementation is split into separate files for different platforms
to allow for cleaner code handling the different possible locations and
path schemes used, including all possible legacy locations and plugin
bundle formats.

This change introduces new locations and directory formats for plugins:
* WINDOWS: Portable plugins need to be placed in a directory called
  "plugins" adjacent to the "bin" directory of an OBS Studio
  installation.
* WINDOWS: Plugins need to be packaged into their own directory
  "bundles", with the associated DLL in the root of the bundle and the
  associated "data" directory next to it.
* WINDOWS: The "obs-plugins" directory adjacent to the "bin" directory
  is deprecated and considered a legacy location for all plugins.
* MACOS: No changes in locations or formats.
* LINUX: Plugins distributed via system package managers now need to use
  the "obs-modules/plugins" directory in the system library directory
  for binaries, and the "obs/obs-modules/plugins" directory in the
  system data directory for resources.
* LINUX: The "obs-plugins" directories in the system library and system
  data directories are deprecated.
* LINUX: Plugins distributed outside of system packages need to use the
  same directory format as plugins for Windows (see above).
* LINUX: A "portable" plugin location like on Windows is not supported.
* LINUX: Proper XDG directory locations are scanned for plugins, using
  XDG_CONFIG_HOME for the legacy plugin format and XDG_DATA_HOME for the
  new plugin structure.
* LINUX: Changes Flatpak plugin loading to use the new plugin structure
  (while still supporting the legacy structure), and moves Flatpak
  specific plugin loading to its own source files.
* ALL PLATFORMS: A single environment variable "OBS_PLUGINS_PATH" can
  now be used to provide a highest-priority location from which to load
  plugins. This directory needs to contain plugin bundles per the new
  format. The one exception is Flatpak which does not support loading
  plugins from an env provided location.
* ALL PLATFORMS: A custom legacy plugin locations can still be provided
  via the "OBS_LEGACY_PLUGINS_PATH" and "OBS_LEGACY_PLUGINS_DATA_PATH"
  environment variables. These are only compatible with plugins using
  the legacy format and are considered deprecated. The one exception is
  Flatpak which does not support loading plugins from an env provided
  location.

Co-authored-by: PatTheMav <PatTheMav@users.noreply.github.com>
This commit is contained in:
FiniteSingularity
2026-09-23 18:53:19 -04:00
committed by Ryan Foster
co-authored by PatTheMav
parent 23bba4413b
commit 11a8445aa4
24 changed files with 1476 additions and 274 deletions
+2 -1
View File
@@ -25,7 +25,7 @@
"versions": "stable;test",
"subdirectories": true,
"add-ld-path": "lib",
"merge-dirs": "lib/obs-plugins;share/obs/obs-plugins",
"merge-dirs": "lib/obs-modules/plugins;share/obs/obs-modules/plugins;lib/obs-plugins;share/obs/obs-plugins",
"no-autodownload": true,
"autodelete": true
},
@@ -132,6 +132,7 @@
"builddir": true,
"config-opts": [
"-DCMAKE_BUILD_TYPE=RelWithDebInfo",
"-DENABLE_FLATPAK=ON",
"-DENABLE_WAYLAND=ON",
"-DENABLE_BROWSER=ON",
"-DCEF_ROOT_DIR=/app/cef",
-4
View File
@@ -99,10 +99,6 @@ foreach(graphics_library IN ITEMS opengl metal d3d11)
endif()
endforeach()
get_property(obs_module_list GLOBAL PROPERTY OBS_MODULES_ENABLED)
list(JOIN obs_module_list "|" SAFE_MODULES)
target_compile_definitions(obs-studio PRIVATE "SAFE_MODULES=\"${SAFE_MODULES}\"")
get_target_property(target_sources obs-studio SOURCES)
set(target_cpp_sources ${target_sources})
set(target_hpp_sources ${target_sources})
+49 -9
View File
@@ -90,6 +90,35 @@ typedef struct UncleanLaunchAction {
bool sendCrashReport = false;
} UncleanLaunchAction;
enum class PluginFailureAction { Continue, OpenPluginManager };
PluginFailureAction handlePluginFailure()
{
QMessageBox pluginWarning;
pluginWarning.setIcon(QMessageBox::Warning);
pluginWarning.setWindowTitle(QTStr("PluginFailure.Dialog.Title"));
pluginWarning.setText(QTStr("PluginFailure.Labels.Text"));
QPushButton *continueButton =
pluginWarning.addButton(QTStr("PluginFailure.Dialog.Continue"), QMessageBox::RejectRole);
QPushButton *handleButton =
pluginWarning.addButton(QTStr("PluginFailure.Dialog.Open"), QMessageBox::AcceptRole);
pluginWarning.setDefaultButton(continueButton);
pluginWarning.exec();
bool openPluginManager = pluginWarning.clickedButton() == handleButton;
if (openPluginManager) {
return PluginFailureAction::OpenPluginManager;
} else {
return PluginFailureAction::Continue;
}
}
UncleanLaunchAction handleUncleanShutdown(bool enableCrashUpload)
{
UncleanLaunchAction launchAction;
@@ -2062,16 +2091,27 @@ void OBSApp::addLogLine(int logLevel, const QString &message)
emit logLineAdded(logLevel, message);
}
void OBSApp::loadAppModules(struct obs_module_failure_info &mfi)
void OBSApp::loadAppModules()
{
pluginManager_->preLoad();
blog(LOG_INFO, "---------------------------------");
obs_load_all_modules2(&mfi);
blog(LOG_INFO, "---------------------------------");
obs_log_loaded_modules();
blog(LOG_INFO, "---------------------------------");
obs_post_load_modules();
pluginManager_->postLoad();
using PluginMode = OBS::PluginManager::Mode;
PluginMode mode = (disable_3p_plugins || safe_mode) ? PluginMode::CoreOnly : PluginMode::Full;
pluginManager_->setPluginMode(mode);
pluginManager_->loadAllPlugins(portable_mode);
}
void OBSApp::handlePluginLoadState()
{
using PluginState = OBS::PluginManager::State;
PluginState loadState = pluginManager_->loadState();
if (loadState != PluginState::Success) {
PluginFailureAction action = handlePluginFailure();
if (action == PluginFailureAction::OpenPluginManager) {
pluginManagerOpenDialog();
}
}
}
void OBSApp::pluginManagerOpenDialog()
+2 -1
View File
@@ -235,12 +235,13 @@ public:
static void sigQuitSignalHandler(int);
#endif
void loadAppModules(struct obs_module_failure_info &mfi);
void loadAppModules();
ThumbnailManager *thumbnails() const { return thumbnailManager; }
// Plugin Manager Accessors
void pluginManagerOpenDialog();
void handlePluginLoadState();
public slots:
void addLogLine(int logLevel, const QString &message);
@@ -1,5 +1,13 @@
find_package(nlohmann_json 3.11 REQUIRED)
set(OBS_PLATFORM_INSTALL_PATH "${CMAKE_INSTALL_PREFIX}/")
set(OBS_PLATFORM_LIBRARY_PATH "${CMAKE_INSTALL_LIBDIR}/")
set(OBS_PLATFORM_DATA_PATH "${CMAKE_INSTALL_DATAROOTDIR}/")
set(OBS_PLATFORM_CORE_MODULE_PATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/obs-modules/core")
set(OBS_PLATFORM_CORE_DATA_PATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/obs/obs-modules/core")
set(OBS_PLATFORM_PLUGIN_MODULE_PATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/obs-modules/plugins")
set(OBS_PLATFORM_PLUGIN_DATA_PATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/obs/obs-modules/plugins")
target_sources(
obs-studio
PRIVATE
@@ -7,6 +15,22 @@ target_sources(
plugin-manager/PluginManager.hpp
plugin-manager/PluginManagerWindow.cpp
plugin-manager/PluginManagerWindow.hpp
plugin-manager/PluginModuleLoader.hpp
)
if(OS_WINDOWS)
target_sources(obs-studio PRIVATE plugin-manager/PluginModuleLoader_Windows.cpp)
elseif(OS_MACOS)
target_sources(obs-studio PRIVATE plugin-manager/PluginModuleLoader_MacOS.mm)
elseif(OS_FLATPAK)
configure_file(plugin-manager/LoaderPaths_Flatpak.hpp.in LoaderPaths_Flatpak.hpp @ONLY)
target_sources(obs-studio PRIVATE plugin-manager/PluginModuleLoader_Flatpak.cpp LoaderPaths_Flatpak.hpp)
elseif(OS_LINUX)
configure_file(plugin-manager/LoaderPaths_Linux.hpp.in LoaderPaths_Linux.hpp @ONLY)
target_sources(obs-studio PRIVATE plugin-manager/PluginModuleLoader_Linux.cpp LoaderPaths_Linux.hpp)
elseif(OS_FREEBSD OR OS_OPENBSD)
configure_file(plugin-manager/LoaderPaths_BSD.hpp.in LoaderPaths_BSD.hpp @ONLY)
target_sources(obs-studio PRIVATE plugin-manager/PluginModuleLoader_BSD.cpp LoaderPaths_BSD.hpp)
endif()
target_link_libraries(obs-studio PRIVATE nlohmann_json::nlohmann_json)
+6
View File
@@ -141,6 +141,12 @@ CrashHandling.Buttons.LaunchNormal="Run in Normal Mode"
CrashHandling.Errors.UploadJSONError="An error occurred while trying to upload the most recent crash log. Please try again later."
CrashHandling.Errors.Title="Crash Log Upload Error"
# Plugin Manager Load Failure
PluginFailure.Dialog.Title="OBS Studio Plugin Loading Failure"
PluginFailure.Labels.Text="OBS Studio was not able to load all third-party plugins.\n\nOpen Plugin Manager to check the state of third-party plugins?"
PluginFailure.Dialog.Continue="Continue"
PluginFailure.Dialog.Open="Open Plugin Manager"
# Safe Mode Restart Option
SafeMode.Restart="Do you want to restart OBS in Safe Mode (third-party plugins, scripting, and WebSockets disabled)?"
SafeMode.RestartNormal="Do you want to restart OBS in Normal Mode?"
@@ -0,0 +1,28 @@
/******************************************************************************
Copyright (C) 2026 by FiniteSingularity <finitesingularityttv@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#pragma once
#include <string_view>
namespace OBS::Constants {
inline constexpr std::string_view kPlatformInstallPath{"@OBS_PLATFORM_INSTALL_PATH@"};
inline constexpr std::string_view kPlatformLibraryPath{"@OBS_PLATFORM_LIBRARY_PATH@"};
inline constexpr std::string_view kPlatformDataPath{"@OBS_PLATFORM_DATA_PATH@"};
inline constexpr std::string_view kPlatformPluginModulePath{"@OBS_PLATFORM_PLUGIN_MODULE_PATH@"};
inline constexpr std::string_view kPlatformPluginDataPath{"@OBS_PLATFORM_PLUGIN_DATA_PATH@"};
} // namespace OBS::Constants
@@ -0,0 +1,30 @@
/******************************************************************************
Copyright (C) 2026 by FiniteSingularity <finitesingularityttv@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#pragma once
#include <string_view>
namespace OBS::Constants {
inline constexpr std::string_view kPlatformInstallPath{"@OBS_PLATFORM_INSTALL_PATH@"};
inline constexpr std::string_view kPlatformLibraryPath{"@OBS_PLATFORM_LIBRARY_PATH@"};
inline constexpr std::string_view kPlatformDataPath{"@OBS_PLATFORM_DATA_PATH@"};
inline constexpr std::string_view kPlatformPluginModulePath{"@OBS_PLATFORM_PLUGIN_MODULE_PATH@"};
inline constexpr std::string_view kPlatformPluginDataPath{"@OBS_PLATFORM_PLUGIN_DATA_PATH@"};
inline constexpr std::string_view kXDGConfigHomeVariable{"XDG_CONFIG_HOME"};
inline constexpr std::string_view kXDGDataHomeVariable{"XDG_DATA_HOME"};
} // namespace OBS::Constants
@@ -0,0 +1,30 @@
/******************************************************************************
Copyright (C) 2026 by FiniteSingularity <finitesingularityttv@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#pragma once
#include <string_view>
namespace OBS::Constants {
inline constexpr std::string_view kPlatformInstallPath{"@OBS_PLATFORM_INSTALL_PATH@"};
inline constexpr std::string_view kPlatformLibraryPath{"@OBS_PLATFORM_LIBRARY_PATH@"};
inline constexpr std::string_view kPlatformDataPath{"@OBS_PLATFORM_DATA_PATH@"};
inline constexpr std::string_view kPlatformPluginModulePath{"@OBS_PLATFORM_PLUGIN_MODULE_PATH@"};
inline constexpr std::string_view kPlatformPluginDataPath{"@OBS_PLATFORM_PLUGIN_DATA_PATH@"};
inline constexpr std::string_view kXDGConfigHomeVariable{"XDG_CONFIG_HOME"};
inline constexpr std::string_view kXDGDataHomeVariable{"XDG_DATA_HOME"};
} // namespace OBS::Constants
+77 -4
View File
@@ -39,7 +39,7 @@ void addModuleToPluginManagerImpl(void *param, obs_module_t *newModule)
std::string moduleName = obs_get_module_file_name(newModule);
moduleName = moduleName.substr(0, moduleName.rfind("."));
if (!obs_get_module_allow_disable(moduleName.c_str())) {
if (obs_is_core_module(newModule)) {
return;
}
@@ -67,7 +67,7 @@ constexpr std::string_view OBSPluginManagerModulesFile = "modules.json";
void PluginManager::preLoad()
{
loadModules_();
loadModuleConfiguration_();
disableModules_();
}
@@ -82,6 +82,32 @@ void PluginManager::postLoad()
linkUnloadedModules_();
}
void PluginManager::loadAllPlugins(bool usePortableMode)
{
preLoad();
bool coreModuleLoadSuccess = obs_load_core_modules();
if (!coreModuleLoadSuccess) {
loadState_ = State::Failure;
//TODO: Replace with structured exception type - https://github.com/obsproject/obs-studio/issues/13394
throw "Failed to load core OBS modules. OBS cannot run without these modules. Please try reinstalling OBS.";
}
if (loadMode_ == Mode::Full) {
blog(LOG_INFO, "---------------------------------");
State pluginState = loadPlugins(usePortableMode);
State legacyPluginState = loadLegacyPlugins(usePortableMode);
loadState_ = (pluginState && legacyPluginState) ? State::Success : State::PartialFailure;
}
blog(LOG_INFO, "---------------------------------");
obs_log_loaded_modules();
blog(LOG_INFO, "---------------------------------");
obs_post_load_modules();
postLoad();
}
std::filesystem::path PluginManager::getConfigFilePath_()
{
std::filesystem::path path = App()->userPluginManagerSettingsLocation /
@@ -90,7 +116,7 @@ std::filesystem::path PluginManager::getConfigFilePath_()
return path;
}
void PluginManager::loadModules_()
void PluginManager::loadModuleConfiguration_()
{
auto modulesFile = getConfigFilePath_();
if (std::filesystem::exists(modulesFile)) {
@@ -282,7 +308,12 @@ void PluginManager::disableModules_()
void PluginManager::open()
{
auto main = OBSBasic::Get();
PluginManagerWindow pluginManagerWindow(modules_, main);
PluginManagerWindow pluginManagerWindow(modules_, failedModules_, main);
if (loadState_ == State::PartialFailure) {
pluginManagerWindow.setPage(PluginManagerWindow::Page::Failure);
}
auto result = pluginManagerWindow.exec();
if (result == QDialog::Accepted) {
modules_ = pluginManagerWindow.result();
@@ -310,3 +341,45 @@ void PluginManager::open()
}
}; // namespace OBS
bool operator&&(const OBS::PluginManager::State &lhs, const OBS::PluginManager::State &rhs)
{
using State = OBS::PluginManager::State;
if (lhs == State::Success && rhs == State::Success) {
return true;
}
return false;
}
bool operator&&(const OBS::PluginManager::State &lhs, bool rhs)
{
return (lhs == OBS::PluginManager::State::Success && rhs);
}
bool operator&&(bool lhs, const OBS::PluginManager::State &rhs)
{
return (lhs && rhs == OBS::PluginManager::State::Success);
}
bool operator||(const OBS::PluginManager::State &lhs, const OBS::PluginManager::State &rhs)
{
using State = OBS::PluginManager::State;
if (lhs == State::Success || rhs == State::Success) {
return true;
}
return false;
}
bool operator||(const OBS::PluginManager::State &lhs, bool rhs)
{
return (lhs == OBS::PluginManager::State::Success || rhs);
}
bool operator||(bool lhs, const OBS::PluginManager::State &rhs)
{
return (lhs || rhs == OBS::PluginManager::State::Success);
}
+32 -5
View File
@@ -43,22 +43,41 @@ struct ModuleInfo {
};
class PluginManager {
public:
using ModuleList = std::vector<std::string>;
enum class Mode { CoreOnly, Full };
enum class State { Failure, PartialFailure, Success };
private:
Mode loadMode_{Mode::Full};
State loadState_{State::Failure};
std::vector<ModuleInfo> modules_ = {};
std::vector<std::string> disabledSources_ = {};
std::vector<std::string> disabledOutputs_ = {};
std::vector<std::string> disabledServices_ = {};
std::vector<std::string> disabledEncoders_ = {};
ModuleList failedModules_ = {};
ModuleList disabledSources_ = {};
ModuleList disabledOutputs_ = {};
ModuleList disabledServices_ = {};
ModuleList disabledEncoders_ = {};
std::filesystem::path getConfigFilePath_();
void loadModules_();
void loadModuleConfiguration_();
void saveModules_();
void disableModules_();
void addModuleTypes_();
void linkUnloadedModules_();
State loadPlugins(bool usePortableMode);
State loadLegacyPlugins(bool usePortableMode);
public:
Mode pluginMode() { return loadMode_; }
void setPluginMode(Mode mode) { loadMode_ = mode; }
State loadState() { return loadState_; }
void disablePlugins();
void preLoad();
void postLoad();
void loadAllPlugins(bool usePortableMode);
void open();
friend void addModuleToPluginManagerImpl(void *param, obs_module_t *newModule);
@@ -68,6 +87,14 @@ void addModuleToPluginManagerImpl(void *param, obs_module_t *newModule);
}; // namespace OBS
bool operator&&(const OBS::PluginManager::State &lhs, const OBS::PluginManager::State &rhs);
bool operator&&(const OBS::PluginManager::State &lhs, bool rhs);
bool operator&&(bool lhs, const OBS::PluginManager::State &rhs);
bool operator||(const OBS::PluginManager::State &lhs, const OBS::PluginManager::State &rhs);
bool operator||(const OBS::PluginManager::State &lhs, bool rhs);
bool operator||(bool lhs, const OBS::PluginManager::State &rhs);
// Anonymous namespace function to add module to plugin manager
// via libobs's module enumeration.
namespace {
@@ -33,7 +33,9 @@ extern bool safe_mode;
namespace OBS {
PluginManagerWindow::PluginManagerWindow(std::vector<ModuleInfo> const &modules, QWidget *parent)
PluginManagerWindow::PluginManagerWindow(std::vector<ModuleInfo> const &modules,
std::vector<std::string> const &failedModules, QWidget *parent)
: QDialog(parent),
modules_(modules),
ui(new Ui::PluginManagerWindow)
@@ -61,6 +63,9 @@ PluginManagerWindow::PluginManagerWindow(std::vector<ModuleInfo> const &modules,
QListWidgetItem *installed = new QListWidgetItem(QTStr("PluginManager.Section.Manage"));
ui->sectionList->addItem(installed);
QListWidgetItem *failed = new QListWidgetItem("Failed");
ui->sectionList->addItem(failed);
QListWidgetItem *updates = new QListWidgetItem(QTStr("PluginManager.Section.Updates"));
updates->setFlags(updates->flags() & ~Qt::ItemIsEnabled);
updates->setFlags(updates->flags() & ~Qt::ItemIsSelectable);
@@ -87,22 +92,33 @@ PluginManagerWindow::PluginManagerWindow(std::vector<ModuleInfo> const &modules,
int row = 0;
int missingIndex = -1;
for (auto &metadata : modules_) {
std::string id = metadata.module_name;
std::string_view id{metadata.module_name};
// Check if the module is missing:
bool missing = !obs_get_module(id.c_str()) && !obs_get_disabled_module(id.c_str());
obs_module_t *moduleData = obs_get_module(id.data());
if (!moduleData) {
moduleData = obs_get_disabled_module(id.data());
}
bool isMissingModule = !moduleData;
bool isLegacyModule = !isMissingModule && obs_is_legacy_module(moduleData);
QString name = !metadata.display_name.empty() ? metadata.display_name.c_str()
: metadata.module_name.c_str();
if (missing && missingIndex == -1) {
if (isMissingModule && missingIndex == -1) {
missingIndex = row;
}
if (isLegacyModule) {
name += " LEGACY";
}
auto item = new QCheckBox(name);
item->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
item->setChecked(metadata.enabled);
if (!metadata.enabledAtLaunch || missing) {
if (!metadata.enabledAtLaunch || isMissingModule) {
item->setProperty("class", "text-muted");
}
@@ -116,6 +132,19 @@ PluginManagerWindow::PluginManagerWindow(std::vector<ModuleInfo> const &modules,
row++;
}
QLabel *item = new QLabel("FAILED ITEMS");
item->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
ui->modulesList->layout()->addWidget(item);
for (const std::string &moduleName : failedModules) {
QString name = QString::fromStdString(moduleName);
QLabel *item = new QLabel(name);
item->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
item->setProperty("class", "text-muted");
ui->modulesList->layout()->addWidget(item);
}
QVBoxLayout *layout = qobject_cast<QVBoxLayout *>(ui->modulesList->layout());
if (safe_mode) {
QLabel *safeModeLabel = new QLabel(ui->modulesList);
@@ -175,4 +204,18 @@ bool PluginManagerWindow::isEnabledPluginsChanged()
return result;
}
void PluginManagerWindow::setPage(Page page)
{
switch (page) {
case Page::Installed:
ui->sectionList->setCurrentRow(1);
break;
case Page::Failure:
ui->sectionList->setCurrentRow(2);
break;
default:
break;
}
}
}; // namespace OBS
@@ -30,9 +30,14 @@ class PluginManagerWindow : public QDialog {
std::unique_ptr<Ui::PluginManagerWindow> ui;
public:
explicit PluginManagerWindow(std::vector<ModuleInfo> const &modules, QWidget *parent = nullptr);
enum class Page { Installed, Failure };
explicit PluginManagerWindow(std::vector<ModuleInfo> const &modules,
std::vector<std::string> const &failedModules, QWidget *parent = nullptr);
inline std::vector<ModuleInfo> const result() { return modules_; }
void setPage(Page page);
private:
std::vector<ModuleInfo> modules_;
@@ -0,0 +1,66 @@
/******************************************************************************
Copyright (C) 2026 by FiniteSingularity <finitesingularityttv@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#pragma once
#include <obs.h>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
using FailureInfo = obs_module_failure_info;
using ModuleLoadInfo = obs_runtime_module_info;
using ModuleList = std::vector<std::string>;
inline constexpr std::string_view kPathVariable{"OBS_PLUGINS_PATH"};
inline constexpr std::string_view kLegacyBinaryPathVariable{"OBS_LEGACY_PLUGINS_PATH"};
inline constexpr std::string_view kLegacyDataPathVariable{"OBS_LEGACY_PLUGINS_DATA_PATH"};
inline std::string getEnvironmentVariable(std::string_view variableName)
{
std::unique_ptr<char> variablePointer{};
variablePointer.reset(getenv(variableName.data()));
if (!variablePointer) {
return {};
}
std::string result{variablePointer.release()};
return result;
}
inline int loadPluginsByInfo(const ModuleLoadInfo &info, ModuleList &failedModules)
{
FailureInfo result = {0};
obs_load_plugins(const_cast<ModuleLoadInfo *>(std::addressof(info)), std::addressof(result));
for (size_t i = 0; i < result.count; ++i) {
const char *failedPluginName = result.failed_modules[i];
if (failedPluginName && *failedPluginName) {
failedModules.emplace_back(failedPluginName);
}
}
obs_module_failure_info_free(std::addressof(result));
return static_cast<int>(result.count);
}
@@ -0,0 +1,220 @@
/******************************************************************************
Copyright (C) 2026 by FiniteSingularity <finitesingularityttv@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include "PluginModuleLoader.hpp"
#include "PluginManager.hpp"
#include <LoaderPaths_BSD.hpp>
#include <obs.h>
#include <util/dstr.h>
#include <util/platform.h>
#include <array>
#include <string>
#include <string_view>
#include <vector>
// BSD Third-Party Plugin Locations
//
// * User Path: <Environment Variable>
// * Binary: <Environment Variable>/<Plugin>/<Plugin>.so
// * Data: <Environment Variable>/<Plugin>/data
//
// * System Root Path: /usr
// * Binary: <System Root Path>/<System Library>/obs-modules/plugins/<Plugin>.so
// * Data: <System Root Path>/share/obs/obs-modules/plugins/<Plugin>
//
// * User Root Path: /home/<User>/.config
// * Binary: <User Root Path>/obs-studio/plugins/<Plugin>/<Plugin>.so
// * Data: <User Root Path>/obs-studio/plugins/<Plugin>/data
//
// * Legacy User Path: <Environment Variable> + <Environment Data Variable>
// * Binary: <Environment Variable>/<Plugin>.so
// * Data: <Environment Data Variable>/<Plugin>
//
// * Legacy System Root Path: /usr
// * Binary: <Legacy System Root Path>/<System Library>/obs-plugins/<Plugin>.so
// * Data: <Legacy System Root Path>/share/obs/obs-plugins/<Plugin>
//
// * Legacy User Root Path: /home/<User>/.config
// * Binary: <Legacy User Root Path>/obs-studio/plugins/<Plugin>/bin/64bit/<Plugin>.so
// * Data: <Legacy User Root Path>/obs-studio/plugins/<Plugin>/data
//
// * Legacy Fallback Path: <OBS Binary Location>
// * Binary: <Legacy Fallback Path>/../../obs-plugins/64bit/<Plugin>.so
// * Data: <Legacy System Root Path>/share/obs/obs-plugins/<Plugin>
//
using State = OBS::PluginManager::State;
using ModuleType = obs_runtime_module_type;
namespace Constants = OBS::Constants;
constexpr std::string_view kModulePathSuffix{"/%module%/"};
constexpr std::string_view kModuleDataPathSuffix{"/%module%/data/"};
constexpr std::string_view kConfigBinaryPath{"/obs-studio/plugins/%module%/"};
constexpr std::string_view kConfigDataPath{"/obs-studio/plugins/%module%/data/"};
constexpr std::string_view kLegacyConfigBinaryPath{"/obs-studio/plugins/%module%/bin/64bit"};
constexpr std::string_view kLegacyConfigDataPath{"/obs-studio/plugins/%module%/data"};
constexpr bool hasSystemPluginPath = !Constants::kPlatformPluginModulePath.empty() &&
!Constants::kPlatformPluginDataPath.empty();
namespace {
State pluginLoadHelper(const ModuleLoadInfo &info, ModuleList &failedModules)
{
int failedModuleCount = loadPluginsByInfo(info, failedModules);
State result = (failedModuleCount > 0) ? State::PartialFailure : State::Success;
return result;
}
} // namespace
namespace OBS {
State PluginManager::loadPlugins(bool usePortableMode)
{
State userPluginState = State::Failure;
std::string userPluginPath = getEnvironmentVariable(kPathVariable);
if (!userPluginPath.empty()) {
std::string binaryPath{userPluginPath};
std::string dataPath{userPluginPath};
binaryPath.append(kModulePathSuffix);
dataPath.append(kModuleDataPathSuffix);
ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()},
.type = OBS_MODULE_TYPE_PLUGIN,
.name = nullptr};
userPluginState = pluginLoadHelper(info, failedModules_);
} else {
userPluginState = State::Success;
}
State systemPluginState = State::Failure;
if constexpr (hasSystemPluginPath) {
std::string binaryPath{Constants::kPlatformPluginModulePath};
std::string dataPath{Constants::kPlatformPluginDataPath};
dataPath.append(kModulePathSuffix);
ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()},
.type = OBS_MODULE_TYPE_PLUGIN,
.name = nullptr};
systemPluginState = pluginLoadHelper(info, failedModules_);
} else {
systemPluginState = State::Success;
}
State configPluginState = State::Failure;
if (!usePortableMode) {
std::string userConfigPath{};
userConfigPath.resize(PATH_MAX);
int bufferSize = os_get_config_path(userConfigPath.data(), userConfigPath.size(), nullptr);
if (bufferSize > 0) {
userConfigPath.resize(bufferSize);
std::string binaryPath{userConfigPath};
std::string dataPath{userConfigPath};
binaryPath.append(kConfigBinaryPath);
dataPath.append(kConfigDataPath);
ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()},
.type = OBS_MODULE_TYPE_PLUGIN,
.name = nullptr};
configPluginState = pluginLoadHelper(info, failedModules_);
}
} else {
configPluginState = State::Success;
}
return (userPluginState && systemPluginState && configPluginState) ? State::Success : State::PartialFailure;
}
State PluginManager::loadLegacyPlugins(bool usePortableMode)
{
State userPluginState = State::Failure;
std::string userPluginPath = getEnvironmentVariable(kLegacyBinaryPathVariable);
std::string userDataPath = getEnvironmentVariable(kLegacyDataPathVariable);
if (!userPluginPath.empty() && !userDataPath.empty()) {
std::string binaryPath{userPluginPath};
std::string dataPath{userDataPath};
dataPath.append(kModulePathSuffix);
ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()},
.type = OBS_MODULE_TYPE_LEGACY_PLUGIN,
.name = nullptr};
userPluginState = pluginLoadHelper(info, failedModules_);
} else {
userPluginState = State::Success;
}
State systemPluginState = State::Failure;
{
std::string binaryPath{Constants::kPlatformInstallPath};
std::string dataPath{Constants::kPlatformInstallPath};
binaryPath.append(Constants::kPlatformLibraryPath);
binaryPath.append("obs-plugins/");
dataPath.append(Constants::kPlatformDataPath);
dataPath.append("obs/obs-plugins/%module%");
ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()},
.type = OBS_MODULE_TYPE_LEGACY_PLUGIN,
.name = nullptr};
systemPluginState = pluginLoadHelper(info, failedModules_);
}
State configPluginState = State::Failure;
if (!usePortableMode) {
std::string userConfigPath{};
userConfigPath.resize(PATH_MAX);
int bufferSize = os_get_config_path(userConfigPath.data(), userConfigPath.size(), nullptr);
if (bufferSize > 0) {
userConfigPath.resize(bufferSize);
std::string binaryPath{userConfigPath};
std::string dataPath{userConfigPath};
binaryPath.append(kLegacyConfigBinaryPath);
dataPath.append(kLegacyConfigDataPath);
ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()},
.type = OBS_MODULE_TYPE_LEGACY_PLUGIN,
.name = nullptr};
configPluginState = pluginLoadHelper(info, failedModules_);
} else {
configPluginState = State::Success;
}
}
return (userPluginState && systemPluginState && configPluginState) ? State::Success : State::PartialFailure;
}
} // namespace OBS
@@ -0,0 +1,142 @@
/******************************************************************************
Copyright (C) 2026 by FiniteSingularity <finitesingularityttv@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include "PluginModuleLoader.hpp"
#include "PluginManager.hpp"
#include <LoaderPaths_Flatpak.hpp>
#include <obs.h>
#include <util/dstr.h>
#include <util/platform.h>
#include <array>
#include <string>
#include <string_view>
#include <vector>
// Flatpak Third-Party Plugin Locations
//
// * Flatpak Extension Point Path: /app/plugins
// * Binary: /app/plugins/<System Library Path>/obs-modules/plugins/<Plugin>.so
// * Data: /app/plugins/<System Data Path>/share/obs/obs-modules/plugins/<Plugin>
//
// * Legacy Flatpak Root Path: /app/plugins
// * Binary: <Legacy Flatpak Root Path>/<System Library Path>/obs-plugins/<Plugin>.so
// * Data: <Legacy Flatpak Root Path>/share/obs/obs-plugins/<Plugin>
//
// * XDG Data Home Path:
// * Binary: <XDG Data Home Path>/obs-studio/plugins/<Plugin>/<Plugin>.so
// * Data: <XDG Data Home Path>/obs-studio/plugins/<Plugin>/data
//
// * XDG Config Home Path:
// * Binary: <Legacy User Root Path>/obs-studio/plugins/<Plugin>/bin/64bit/<Plugin>.so
// * Data: <Legacy User Root Path>/obs-studio/plugins/<Plugin>/data
using State = OBS::PluginManager::State;
using ModuleType = obs_runtime_module_type;
namespace Constants = OBS::Constants;
constexpr std::string_view kConfigBinaryPath{"/obs-studio/plugins/%module%/"};
constexpr std::string_view kConfigDataPath{"/obs-studio/plugins/%module%/data/"};
constexpr std::string_view kLegacyConfigBinaryPath{"/obs-studio/plugins/%module%/bin/64bit"};
constexpr std::string_view kLegacyConfigDataPath{"/obs-studio/plugins/%module%/data"};
constexpr std::string_view kFlatpakBasePath{"/app/plugins/"};
namespace {
State pluginLoadHelper(const ModuleLoadInfo &info, ModuleList &failedModules)
{
int failedModuleCount = loadPluginsByInfo(info, failedModules);
State result = (failedModuleCount > 0) ? State::PartialFailure : State::Success;
return result;
}
} // namespace
namespace OBS {
State PluginManager::loadPlugins(bool /*usePortableMode*/)
{
State flatpakPluginState = State::Failure;
{
std::string binaryPath{kFlatpakBasePath};
std::string dataPath{kFlatpakBasePath};
binaryPath.append(Constants::kPlatformLibraryPath);
binaryPath.append("obs-modules/plugins/");
dataPath.append(Constants::kPlatformDataPath);
dataPath.append("obs-modules/plugins/%module%");
ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()},
.type = OBS_MODULE_TYPE_PLUGIN,
.name = nullptr};
flatpakPluginState = pluginLoadHelper(info, failedModules_);
}
State xdgDataHomeState = State::Failure;
std::string xdgDataHomePath = getEnvironmentVariable(Constants::kXDGDataHomeVariable);
if (!xdgDataHomePath.empty()) {
std::string binaryPath{xdgDataHomePath};
std::string dataPath{xdgDataHomePath};
binaryPath.append(kConfigBinaryPath);
dataPath.append(kConfigDataPath);
ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()},
.type = OBS_MODULE_TYPE_PLUGIN,
.name = nullptr};
xdgDataHomeState = pluginLoadHelper(info, failedModules_);
} else {
xdgDataHomeState = State::Success;
}
return (flatpakPluginState && xdgDataHomeState) ? State::Success : State::PartialFailure;
}
State PluginManager::loadLegacyPlugins(bool /*usePortableMode*/)
{
State flatpakPluginState = State::Failure;
{
std::string binaryPath{kFlatpakBasePath};
std::string dataPath{kFlatpakBasePath};
binaryPath.append(Constants::kPlatformLibraryPath);
binaryPath.append("obs-plugins/");
dataPath.append(Constants::kPlatformDataPath);
dataPath.append("obs/obs-plugins/%module%");
ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()},
.type = OBS_MODULE_TYPE_LEGACY_PLUGIN,
.name = nullptr};
flatpakPluginState = pluginLoadHelper(info, failedModules_);
}
State xdgDataConfigState = State::Failure;
std::string xdgConfigHomePath = getEnvironmentVariable(Constants::kXDGConfigHomeVariable);
if (!xdgConfigHomePath.empty()) {
std::string binaryPath{xdgConfigHomePath};
std::string dataPath{xdgConfigHomePath};
binaryPath.append(kLegacyConfigBinaryPath);
dataPath.append(kLegacyConfigDataPath);
ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()},
.type = OBS_MODULE_TYPE_LEGACY_PLUGIN,
.name = nullptr};
xdgDataConfigState = pluginLoadHelper(info, failedModules_);
}
return (flatpakPluginState && xdgDataConfigState) ? State::Success : State::PartialFailure;
}
} // namespace OBS
@@ -0,0 +1,224 @@
/******************************************************************************
Copyright (C) 2026 by FiniteSingularity <finitesingularityttv@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include "PluginModuleLoader.hpp"
#include "PluginManager.hpp"
#include <LoaderPaths_Linux.hpp>
#include <obs.h>
#include <util/dstr.h>
#include <util/platform.h>
#include <array>
#include <string>
#include <string_view>
#include <vector>
// Linux Third-Party Plugin Locations
//
// * User Path: <Environment Variable>
// * Binary: <Environment Variable>/<Plugin>/<Plugin>.so
// * Data: <Environment Variable>/<Plugin>/data
//
// * System Root Path: /usr
// * Binary: <System Root Path>/<System Library>/obs-modules/plugins/<Plugin>.so
// * Data: <System Root Path>/share/obs/obs-modules/plugins/<Plugin>
//
// * User Root Path: $XDG_DATA_HOME
// * Binary: <User Root Path>/obs-studio/plugins/<Plugin>/<Plugin>.so
// * Data: <User Root Path>/obs-studio/plugins/<Plugin>/data
//
// * Legacy User Path: <Environment Variable> + <Environment Data Variable>
// * Binary: <Environment Variable>/<Plugin>.so
// * Data: <Environment Data Variable>/<Plugin>
//
// * Legacy System Root Path: /usr
// * Binary: <Legacy System Root Path>/<System Library>/obs-plugins/<Plugin>.so
// * Data: <Legacy System Root Path>/share/obs/obs-plugins/<Plugin>
//
// * Legacy User Root Path: $XDG_CONFIG_HOME
// * Binary: <Legacy User Root Path>/obs-studio/plugins/<Plugin>/bin/64bit/<Plugin>.so
// * Data: <Legacy User Root Path>/obs-studio/plugins/<Plugin>/data
//
// * Legacy Fallback Path: <OBS Binary Location>
// * Binary: <Legacy Fallback Path>/../../obs-plugins/64bit/<Plugin>.so
// * Data: <Legacy System Root Path>/share/obs/obs-plugins/<Plugin>
using State = OBS::PluginManager::State;
using ModuleType = obs_runtime_module_type;
namespace Constants = OBS::Constants;
constexpr std::string_view kModulePathSuffix{"/%module%/"};
constexpr std::string_view kModuleDataPathSuffix{"/%module%/data/"};
constexpr std::string_view kXdgBinaryPath{"/obs-studio/plugins/%module%/"};
constexpr std::string_view kXdgDataPath{"/obs-studio/plugins/%module%/data/"};
constexpr std::string_view kLegacyXdgBinaryPath{"/obs-studio/plugins/%module%/bin/64bit"};
constexpr std::string_view kLegacyXdgDataPath{"/obs-studio/plugins/%module%/data"};
constexpr bool hasSystemPluginPath = !Constants::kPlatformPluginModulePath.empty() &&
!Constants::kPlatformPluginDataPath.empty();
namespace {
State pluginLoadHelper(const ModuleLoadInfo &info, ModuleList &failedModules)
{
int failedModuleCount = loadPluginsByInfo(info, failedModules);
State result = (failedModuleCount > 0) ? State::PartialFailure : State::Success;
return result;
}
} // namespace
namespace OBS {
State PluginManager::loadPlugins(bool usePortableMode)
{
State userPluginState = State::Failure;
std::string userPluginPath = getEnvironmentVariable(kPathVariable);
if (!userPluginPath.empty()) {
std::string binaryPath{userPluginPath};
std::string dataPath{userPluginPath};
binaryPath.append(kModulePathSuffix);
dataPath.append(kModuleDataPathSuffix);
ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()},
.type = OBS_MODULE_TYPE_PLUGIN,
.name = nullptr};
userPluginState = pluginLoadHelper(info, failedModules_);
} else {
userPluginState = State::Success;
}
State systemPluginState = State::Failure;
if constexpr (hasSystemPluginPath) {
std::string binaryPath{Constants::kPlatformPluginModulePath};
std::string dataPath{Constants::kPlatformPluginDataPath};
dataPath.append(kModulePathSuffix);
ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()},
.type = OBS_MODULE_TYPE_PLUGIN,
.name = nullptr};
systemPluginState = pluginLoadHelper(info, failedModules_);
} else {
systemPluginState = State::Success;
}
State xdgPluginState = State::Failure;
if (!usePortableMode) {
std::string xdgDataHomePath = getEnvironmentVariable(Constants::kXDGDataHomeVariable);
if (xdgDataHomePath.empty()) {
std::string homePath = getEnvironmentVariable("HOME");
if (!homePath.empty()) {
xdgDataHomePath = homePath + "/.local/share";
}
}
if (!xdgDataHomePath.empty()) {
std::string binaryPath{xdgDataHomePath};
std::string dataPath{xdgDataHomePath};
binaryPath.append(kXdgBinaryPath);
dataPath.append(kXdgDataPath);
ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()},
.type = OBS_MODULE_TYPE_PLUGIN,
.name = nullptr};
xdgPluginState = pluginLoadHelper(info, failedModules_);
} else {
xdgPluginState = State::Success;
}
} else {
xdgPluginState = State::Success;
}
return (userPluginState && systemPluginState && xdgPluginState) ? State::Success : State::PartialFailure;
}
State PluginManager::loadLegacyPlugins(bool usePortableMode)
{
State userPluginState = State::Failure;
std::string userPluginPath = getEnvironmentVariable(kLegacyBinaryPathVariable);
std::string userDataPath = getEnvironmentVariable(kLegacyDataPathVariable);
if (!userPluginPath.empty() && !userDataPath.empty()) {
std::string binaryPath{userPluginPath};
std::string dataPath{userDataPath};
dataPath.append(kModulePathSuffix);
ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()},
.type = OBS_MODULE_TYPE_LEGACY_PLUGIN,
.name = nullptr};
userPluginState = pluginLoadHelper(info, failedModules_);
} else {
userPluginState = State::Success;
}
State systemPluginState = State::Failure;
{
std::string binaryPath{Constants::kPlatformInstallPath};
std::string dataPath{Constants::kPlatformInstallPath};
binaryPath.append(Constants::kPlatformLibraryPath);
binaryPath.append("obs-plugins/");
dataPath.append(Constants::kPlatformDataPath);
dataPath.append("obs/obs-plugins/%module%");
ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()},
.type = OBS_MODULE_TYPE_LEGACY_PLUGIN,
.name = nullptr};
systemPluginState = pluginLoadHelper(info, failedModules_);
}
State xdgPluginState = State::Failure;
if (!usePortableMode) {
std::string xdgConfigHomePath = getEnvironmentVariable(Constants::kXDGConfigHomeVariable);
if (xdgConfigHomePath.empty()) {
std::string homePath = getEnvironmentVariable("HOME");
if (!homePath.empty()) {
xdgConfigHomePath = homePath + "/.config";
}
}
if (!xdgConfigHomePath.empty()) {
std::string binaryPath{xdgConfigHomePath};
std::string dataPath{xdgConfigHomePath};
binaryPath.append(kLegacyXdgBinaryPath);
dataPath.append(kLegacyXdgDataPath);
ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()},
.type = OBS_MODULE_TYPE_LEGACY_PLUGIN,
.name = nullptr};
xdgPluginState = pluginLoadHelper(info, failedModules_);
} else {
xdgPluginState = State::Success;
}
} else {
xdgPluginState = State::Success;
}
return (userPluginState && systemPluginState && xdgPluginState) ? State::Success : State::PartialFailure;
}
} // namespace OBS
@@ -0,0 +1,196 @@
/******************************************************************************
Copyright (C) 2026 by FiniteSingularity <finitesingularityttv@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include "PluginModuleLoader.hpp"
#include "PluginManager.hpp"
#include <obs.h>
#include <util/dstr.h>
#include <util/platform.h>
#include <string>
#include <string_view>
#include <vector>
// macOS Third-Party Plugin Locations
//
// * User Path: <Environment Variable>
// * Binary: <Environment Variable>/<Plugin>.plugin/Contents/MacOS
// * Data: <Environment Variable>/<Plugin>.plugin/Contents/Resources
//
// * Root Path: <User>/Library/Application Support
// * Binary: <Root Path>/obs-studio/plugins/<Plugin>.plugin/Contents/MacOS/<Plugin>
// * Data: <Root Path>/obs-studio/plugins/<Plugin>.plugin/Contents/Resources
//
// * Legacy User Path: <Environment Variable> + <Data Environment Variable>
// * Binary: <Environment Variable>/<Plugin>.plugin/Contents/MacOS/<Plugin>
// * Data: <Data Environment Variable>/<Plugin>.plugin/Contents/Resources
//
// * Legacy System Root Path: /Library/Application Support
// * Legacy binary: <Legacy System Root Path>/obs-studio/plugins/<Plugin>/bin/<Plugin>.so
// * Legacy data: <Legacy System Root Path>/obs-studio/plugins/<Plugin>/data
//
// * Legacy User Root Path: <User>/Library/Application Support
// * Legacy binary: <Legacy User Root Path>/obs-studio/plugins/<Plugin>/bin/<Plugin>.so
// * Legacy data: <Legacy User Root Path>/obs-studio/plugins/<Plugin>/data
//
using State = OBS::PluginManager::State;
using ModuleType = obs_runtime_module_type;
constexpr std::string_view kPluginPathSuffix {"obs-studio/plugins/%module%.plugin"};
constexpr std::string_view kUserPluginPathSuffix {"/%module%.plugin"};
constexpr std::string_view kLegacyPluginPathSuffix {"obs-studio/plugins/%module%"};
#ifdef __aarch64__
constexpr bool kIsAppleSilicon = true;
#else
constexpr bool kIsAppleSilicon = false;
#endif
namespace {
State loadPluginsFromPath(const std::string &pathString, ModuleType type, ModuleList &failedModules)
{
std::string binaryPath {pathString};
std::string dataPath {pathString};
switch (type) {
case OBS_MODULE_TYPE_LEGACY_PLUGIN: {
binaryPath.append("/bin");
dataPath.append("/data");
break;
}
default: {
binaryPath.append("/Contents/MacOS");
dataPath.append("/Contents/Resources");
break;
}
}
ModuleLoadInfo info = {
.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()},
.type = type,
.name = nullptr
};
int failedModuleCount = loadPluginsByInfo(info, failedModules);
State result = (failedModuleCount > 0) ? State::PartialFailure : State::Success;
return result;
}
State loadPluginsFromLegacyPaths(const std::string &binaryPathString, const std::string &dataPathString,
ModuleList &failedModules)
{
std::string binaryPath {binaryPathString};
std::string dataPath {dataPathString};
binaryPath.append("/%module%.plugin/Contents/MacOS");
dataPath.append("/%module%.plugin/Contents/Resources");
ModuleLoadInfo info = {
.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()},
.type = OBS_MODULE_TYPE_LEGACY_PLUGIN,
.name = nullptr
};
int failedModuleCount = loadPluginsByInfo(info, failedModules);
State result = (failedModuleCount > 0) ? State::PartialFailure : State::Success;
return result;
}
} // namespace
namespace OBS {
State PluginManager::loadPlugins(bool usePortableMode __unused)
{
State userPluginState = State::Failure;
std::string userPluginPath = getEnvironmentVariable(kPathVariable);
if (!userPluginPath.empty()) {
userPluginPath.append(kUserPluginPathSuffix);
userPluginState = loadPluginsFromPath(userPluginPath, OBS_MODULE_TYPE_PLUGIN, failedModules_);
} else {
userPluginState = State::Success;
}
State libraryPluginState = State::Failure;
std::string appLibraryPath {};
appLibraryPath.resize(PATH_MAX);
int bufferSize = os_get_config_path(appLibraryPath.data(), appLibraryPath.size(), kPluginPathSuffix.data());
if (bufferSize > 0) {
appLibraryPath.resize(bufferSize);
libraryPluginState = loadPluginsFromPath(appLibraryPath, OBS_MODULE_TYPE_PLUGIN, failedModules_);
}
return (userPluginState && libraryPluginState) ? State::Success : State::PartialFailure;
}
State PluginManager::loadLegacyPlugins(bool usePortableMode __unused)
{
State userPluginState = State::Failure;
std::string userPluginPath = getEnvironmentVariable(kLegacyBinaryPathVariable);
std::string userDataPath = getEnvironmentVariable(kLegacyDataPathVariable);
if (!userPluginPath.empty() && !userDataPath.empty()) {
userPluginState = loadPluginsFromLegacyPaths(userPluginPath, userDataPath, failedModules_);
} else {
userPluginState = State::Success;
}
State legacyPluginState = State::Failure;
if constexpr (!kIsAppleSilicon) {
State systemLibraryState = State::Failure;
std::string systemLibraryPath {};
systemLibraryPath.resize(PATH_MAX);
int bufferSize = os_get_program_data_path(systemLibraryPath.data(), systemLibraryPath.size(),
kLegacyPluginPathSuffix.data());
if (bufferSize > 0) {
systemLibraryPath.resize(bufferSize);
systemLibraryState =
loadPluginsFromPath(systemLibraryPath, OBS_MODULE_TYPE_LEGACY_PLUGIN, failedModules_);
}
State appLibrayState = State::Failure;
std::string appLibraryPath {};
appLibraryPath.resize(PATH_MAX);
bufferSize =
os_get_config_path(appLibraryPath.data(), appLibraryPath.size(), kLegacyPluginPathSuffix.data());
if (bufferSize > 0) {
appLibraryPath.resize(bufferSize);
appLibrayState = loadPluginsFromPath(appLibraryPath, OBS_MODULE_TYPE_LEGACY_PLUGIN, failedModules_);
}
legacyPluginState = (systemLibraryState && appLibrayState) ? State::Success : State::PartialFailure;
} else {
legacyPluginState = State::Success;
}
return (userPluginState && legacyPluginState) ? State::Success : State::PartialFailure;
}
} // namespace OBS
@@ -0,0 +1,196 @@
/******************************************************************************
Copyright (C) 2026 by FiniteSingularity <finitesingularityttv@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include "PluginModuleLoader.hpp"
#include "PluginManager.hpp"
#include <obs.h>
#include <util/dstr.h>
#include <util/platform.h>
#include <string>
#include <string_view>
#include <vector>
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#undef WIN32_LEAN_AND_MEAN
// Windows Third-Party Plugin Locations
//
// * User Path: <Environment Variable>
// * Binary: <Environment Variable>/<Plugin>/<Plugin>.dll
// * Data: <Environment Variable>/<Plugin>/data
//
// * Root Path: C:/ProgramData
// * Binary: <Root Path>/obs-studio/plugins/<Plugin>/<Plugin>.dll
// * Data: <Root Path>/obs-studio/plugins/<Plugin>/data
//
// * Portable Root Path: <OBS Binary Location>
// * Binary: <Portable Root Path>/../../plugins/<Plugin>/<Plugin>.dll
// * Data: <Portable Root Path>/../../plugins/<Plugin>/data
//
// * Legacy User Path: <Environment Variable> + <Environment Data Variable>
// * Binary: <Environment Variable>/<Plugin>.dll
// * Data: <Environment Data Variable>/<Plugin>/data
//
// * Legacy Root Path: <OBS Binary Location>
// * Binary: <Legacy Root Path>/../../obs-plugins/<Plugin>.dll
// * Data: <Legacy Root Path>/../../data/obs-plugins/<Plugin>
//
// * Legacy Portable Path: <OBS Binary Location>
// * Binary: <Legacy Root Path>/../../obs-plugins/<Plugin>.dll
// * Data: <Legacy Root Path>/../../data/obs-plugins/<Plugin>
//
// * Legacy System Path: C:/ProgramData
// * Binary: <Legacy System Path>/obs-studio/plugins/<Plugin>/bin/<Plugin>.dll
// * Data: <Legacy System Path>/obs-studio/plugins/<Plugin>/data
//
using State = OBS::PluginManager::State;
using ModuleType = obs_runtime_module_type;
constexpr std::string_view kPluginPathSuffix{"obs-studio/plugins/%module%"};
constexpr std::string_view kUserPluginPathSuffix{"/%module%"};
constexpr std::string_view kPortablePluginPath{"../../plugins/%module%"};
constexpr std::string_view kLegacyPortableBinaryPath{"../../obs-plugins/64bit/"};
constexpr std::string_view kLegacyPortableDataPath{"../../data/obs-plugins/%module%"};
namespace {
State loadPluginsFromPath(const std::string &pathString, ModuleType type, ModuleList &failedModules)
{
std::string binaryPath{pathString};
std::string dataPath{pathString};
switch (type) {
case OBS_MODULE_TYPE_LEGACY_PLUGIN: {
binaryPath.append("/bin/64bit");
dataPath.append("/data");
break;
}
default: {
dataPath.append("/data");
break;
}
}
ModuleLoadInfo info = {0};
info.path_info.binary = binaryPath.c_str();
info.path_info.data = dataPath.c_str();
info.type = type;
info.name = nullptr;
int failedModuleCount = loadPluginsByInfo(info, failedModules);
State result = (failedModuleCount > 0) ? State::PartialFailure : State::Success;
return result;
}
} // namespace
namespace OBS {
State PluginManager::loadPlugins(bool usePortableMode)
{
State userPluginState = State::Failure;
std::string userPluginPath = getEnvironmentVariable(kPathVariable);
if (!userPluginPath.empty()) {
userPluginPath.append(kUserPluginPathSuffix);
userPluginState = loadPluginsFromPath(userPluginPath, OBS_MODULE_TYPE_PLUGIN, failedModules_);
} else {
userPluginState = State::Success;
}
State libraryPluginState = State::Failure;
std::string appLibraryPath{};
if (!usePortableMode) {
appLibraryPath.resize(MAX_PATH);
int bufferSize = os_get_program_data_path(appLibraryPath.data(), appLibraryPath.size(),
kPluginPathSuffix.data());
if (bufferSize > 0) {
appLibraryPath.resize(bufferSize);
}
} else {
appLibraryPath = kPortablePluginPath;
}
libraryPluginState = loadPluginsFromPath(appLibraryPath, OBS_MODULE_TYPE_PLUGIN, failedModules_);
return (userPluginState && libraryPluginState) ? State::Success : State::PartialFailure;
}
State PluginManager::loadLegacyPlugins(bool usePortableMode)
{
State userPluginState = State::Failure;
std::string userPluginPath = getEnvironmentVariable(kLegacyBinaryPathVariable);
std::string userDataPath = getEnvironmentVariable(kLegacyDataPathVariable);
if (!userPluginPath.empty() && !userDataPath.empty()) {
userDataPath.append(kUserPluginPathSuffix);
ModuleLoadInfo info = {0};
info.path_info.binary = userPluginPath.c_str();
info.path_info.data = userDataPath.c_str();
info.type = OBS_MODULE_TYPE_LEGACY_PLUGIN;
info.name = nullptr;
int failedModuleCount = loadPluginsByInfo(info, failedModules_);
userPluginState = (failedModuleCount > 0) ? State::PartialFailure : State::Success;
} else {
userPluginState = State::Success;
}
State portablePluginState = State::Failure;
{
ModuleLoadInfo info = {0};
info.path_info.binary = kLegacyPortableBinaryPath.data();
info.path_info.data = kLegacyPortableDataPath.data();
info.type = OBS_MODULE_TYPE_LEGACY_PLUGIN;
info.name = nullptr;
int failedModuleCount = loadPluginsByInfo(info, failedModules_);
portablePluginState = (failedModuleCount > 0) ? State::PartialFailure : State::Success;
}
State libraryPluginState = State::Failure;
if (!usePortableMode) {
std::string appLibraryPath{};
appLibraryPath.resize(MAX_PATH);
int bufferSize = os_get_program_data_path(appLibraryPath.data(), appLibraryPath.size(),
kPluginPathSuffix.data());
if (bufferSize > 0) {
appLibraryPath.resize(bufferSize);
libraryPluginState =
loadPluginsFromPath(appLibraryPath, OBS_MODULE_TYPE_LEGACY_PLUGIN, failedModules_);
}
} else {
libraryPluginState = State::Success;
}
return (userPluginState && portablePluginState && libraryPluginState) ? State::Success : State::PartialFailure;
}
} // namespace OBS
+8 -176
View File
@@ -66,7 +66,6 @@
#include <sstream>
#endif
#include <string>
#include <unordered_set>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
@@ -113,146 +112,6 @@ namespace {
std::once_flag saveOnceFlag;
}
static void AddExtraModulePaths()
{
string plugins_path, plugins_data_path;
char *s;
s = getenv("OBS_PLUGINS_PATH");
if (s) {
plugins_path = s;
}
s = getenv("OBS_PLUGINS_DATA_PATH");
if (s) {
plugins_data_path = s;
}
if (!plugins_path.empty() && !plugins_data_path.empty()) {
#if defined(__APPLE__)
plugins_path += "/%module%.plugin/Contents/MacOS";
plugins_data_path += "/%module%.plugin/Contents/Resources";
obs_add_module_path(plugins_path.c_str(), plugins_data_path.c_str());
#else
string data_path_with_module_suffix;
data_path_with_module_suffix += plugins_data_path;
data_path_with_module_suffix += "/%module%";
obs_add_module_path(plugins_path.c_str(), data_path_with_module_suffix.c_str());
#endif
}
#if !defined(__APPLE__)
#if defined(_WIN32)
char *thirdPartyPluginPath = os_get_executable_path_ptr("../../obs-plugins/64bit/");
char *thirdPartyDataPath = os_get_executable_path_ptr("../../data/obs-plugins/%module%");
if (thirdPartyPluginPath && *thirdPartyPluginPath && thirdPartyDataPath && *thirdPartyDataPath) {
obs_add_module_path(thirdPartyPluginPath, thirdPartyDataPath);
}
bfree(thirdPartyPluginPath);
bfree(thirdPartyDataPath);
#else
constexpr std::string_view findToken{"obs-modules"};
constexpr std::string_view replaceToken{"obs-plugins"};
constexpr std::string_view thirdPartyDataPath{OBS_INSTALL_DATA_PATH "/obs-plugins/%module%"};
std::string thirdPartyPluginPath{OBS_INSTALL_PREFIX "/" OBS_PLUGIN_DESTINATION};
size_t startPos = thirdPartyPluginPath.find(findToken);
if (startPos != std::string::npos) {
thirdPartyPluginPath.replace(startPos, findToken.length(), replaceToken);
obs_add_module_path(thirdPartyPluginPath.c_str(), thirdPartyDataPath.data());
}
#endif
#endif
if (portable_mode) {
return;
}
char base_module_dir[512];
#if defined(_WIN32)
int ret = GetProgramDataPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%");
#elif defined(__APPLE__)
int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%.plugin");
#else
int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%");
#endif
if (ret <= 0) {
return;
}
string path = base_module_dir;
#if defined(__APPLE__)
/* User Application Support Search Path */
obs_add_module_path((path + "/Contents/MacOS").c_str(), (path + "/Contents/Resources").c_str());
#ifndef __aarch64__
/* Legacy System Library Search Path */
char system_legacy_module_dir[PATH_MAX];
GetProgramDataPath(system_legacy_module_dir, sizeof(system_legacy_module_dir), "obs-studio/plugins/%module%");
std::string path_system_legacy = system_legacy_module_dir;
obs_add_module_path((path_system_legacy + "/bin").c_str(), (path_system_legacy + "/data").c_str());
/* Legacy User Application Support Search Path */
char user_legacy_module_dir[PATH_MAX];
GetAppConfigPath(user_legacy_module_dir, sizeof(user_legacy_module_dir), "obs-studio/plugins/%module%");
std::string path_user_legacy = user_legacy_module_dir;
obs_add_module_path((path_user_legacy + "/bin").c_str(), (path_user_legacy + "/data").c_str());
#endif
#else
#if ARCH_BITS == 64
obs_add_module_path((path + "/bin/64bit").c_str(), (path + "/data").c_str());
#else
obs_add_module_path((path + "/bin/32bit").c_str(), (path + "/data").c_str());
#endif
#endif
}
/* First-party modules considered to be potentially unsafe to load in Safe Mode
* due to them allowing external code (e.g. scripts) to modify OBS's state. */
static const unordered_set<string> unsafe_modules = {
"frontend-tools", // Scripting
"obs-websocket", // Allows outside modifications
};
static void SetSafeModuleNames()
{
#ifndef SAFE_MODULES
return;
#else
string module;
stringstream modules_(SAFE_MODULES);
while (getline(modules_, module, '|')) {
/* When only disallowing third-party plugins, still add
* "unsafe" bundled modules to the safe list. */
if (disable_3p_plugins || !unsafe_modules.count(module)) {
obs_add_safe_module(module.c_str());
}
}
#endif
}
static void SetCoreModuleNames()
{
#ifndef SAFE_MODULES
throw "SAFE_MODULES not defined";
#else
std::string safeModules = SAFE_MODULES;
if (safeModules.empty()) {
throw "SAFE_MODULES is empty";
}
string module;
stringstream modules_(SAFE_MODULES);
while (getline(modules_, module, '|')) {
obs_add_core_module(module.c_str());
}
#endif
}
extern void setupDockAction(QDockWidget *dock);
OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new Ui::OBSBasic)
@@ -1061,27 +920,15 @@ void OBSBasic::OBSInit()
#if defined(_WIN32) && !defined(_DEBUG)
LoadLibraryW(L"Qt6Network");
#endif
struct obs_module_failure_info mfi;
// Safe Mode disables third-party plugins so we don't need to add each path outside the OBS bundle/installation.
if (safe_mode || disable_3p_plugins) {
SetSafeModuleNames();
} else {
AddExtraModulePaths();
}
// Core modules are not allowed to be disabled by the user via plugin manager.
SetCoreModuleNames();
/* Modules can access frontend information (i.e. profile and scene collection data) during their initialization, and some modules (e.g. obs-websockets) are known to use the filesystem location of the current profile in their own code.
Thus the profile and scene collection discovery needs to happen before any access to that information (but after initializing global settings) to ensure legacy code gets valid path information.
*/
// Modules can access frontend information (i.e., profile and scene collection data) during their initialization,
// and some modules (e.g., obs-websockets) are known to use the filesystem location of the current profile in their
// own code.
//
// Thus, the profile and scene collection discovery needs to happen before any access to that information happens,
// but after initializing global settings, to ensure legacy code gets valid path information.
RefreshSceneCollections(true);
App()->loadAppModules(mfi);
BPtr<char *> failed_modules = mfi.failed_modules;
App()->loadAppModules();
#ifdef BROWSER_AVAILABLE
cef = obs_browser_init_panel();
@@ -1399,22 +1246,7 @@ void OBSBasic::OBSInit()
activateWindow();
}
/* ------------------------------------------- */
/* display warning message for failed modules */
if (mfi.count) {
QString failed_plugins;
char **plugin = mfi.failed_modules;
while (*plugin) {
failed_plugins += *plugin;
failed_plugins += "\n";
plugin++;
}
QString failed_msg = QTStr("PluginsFailedToLoad.Text").arg(failed_plugins);
OBSMessageBox::warning(this, QTStr("PluginsFailedToLoad.Title"), failed_msg);
}
App()->handlePluginLoadState();
}
void OBSBasic::OnFirstLoad()
+3 -1
View File
@@ -158,6 +158,7 @@ extern void free_module(struct obs_module *mod);
struct obs_module_path {
char *bin;
char *data;
enum obs_runtime_module_type type;
};
static inline void free_module_path(struct obs_module_path *omp)
@@ -552,7 +553,6 @@ struct obs_core {
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;
@@ -580,6 +580,8 @@ struct obs_core {
os_task_queue_t *destruction_task_thread;
obs_task_handler_t ui_task_handler;
bool core_modules_loaded;
};
extern struct obs_core *obs;
+77 -52
View File
@@ -426,15 +426,6 @@ 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)
@@ -508,12 +499,32 @@ static void load_all_callback(void *param, const struct obs_module_info2 *info)
return;
}
if (is_disabled_module(info->name)) {
if (info->type != OBS_MODULE_TYPE_CORE && 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;
}
/* This is a necessary check until legacy plugin loading is removed in a future update.
* It covers portable installs where the old core modules still live in `obs-plugins`.
*/
if (info->type == OBS_MODULE_TYPE_LEGACY_PLUGIN && is_core_module(info->name)) {
blog(LOG_WARNING, "Skipping module '%s', is a legacy core module", info->name);
return;
}
obs_module_t *existing_module = obs_get_module(info->name);
if (info->type != OBS_MODULE_TYPE_CORE && existing_module != NULL) {
if (info->type == OBS_MODULE_TYPE_LEGACY_PLUGIN) {
blog(LOG_WARNING, "Skipping legacy plugin '%s' at '%s', already loaded from '%s'", info->name,
info->bin_path, existing_module->bin_path);
} else {
blog(LOG_WARNING, "Skipping plugin '%s' at '%s', already loaded from '%s'", info->name,
info->bin_path, existing_module->bin_path);
}
return;
}
int code = obs_open_module(&module, info->bin_path, info->data_path);
switch (code) {
case MODULE_MISSING_EXPORTS:
@@ -540,6 +551,9 @@ static void load_all_callback(void *param, const struct obs_module_info2 *info)
free_module(module);
obs_create_disabled_module(&disabled_module, info->bin_path, info->data_path,
OBS_MODULE_FAILED_TO_INITIALIZE);
disabled_module->module_type = info->type;
} else {
module->module_type = info->type;
}
UNUSED_PARAMETER(param);
@@ -553,53 +567,45 @@ load_failure:
}
}
static const char *obs_load_all_modules_name = "obs_load_all_modules";
#ifdef _WIN32
static const char *reset_win32_symbol_paths_name = "reset_win32_symbol_paths";
#endif
void obs_load_all_modules(void)
{
profile_start(obs_load_all_modules_name);
obs_find_modules2(load_all_callback, NULL);
#ifdef _WIN32
profile_start(reset_win32_symbol_paths_name);
reset_win32_symbol_paths();
profile_end(reset_win32_symbol_paths_name);
#endif
profile_end(obs_load_all_modules_name);
}
static const char *obs_load_all_modules2_name = "obs_load_all_modules2";
void obs_load_all_modules(void) {}
void obs_load_all_modules2(struct obs_module_failure_info *mfi)
{
struct fail_info fail_info = {0};
memset(mfi, 0, sizeof(*mfi));
UNUSED_PARAMETER(mfi);
}
profile_start(obs_load_all_modules2_name);
struct fail_info core_fail_info = {0};
load_core_modules(load_all_callback, &core_fail_info);
if (core_fail_info.fail_count > 0) {
fail_info.fail_count += core_fail_info.fail_count;
dstr_insert_dstr(&fail_info.fail_modules, 0, &core_fail_info.fail_modules);
bool obs_load_core_modules(void)
{
if (obs->core_modules_loaded) {
return true;
}
dstr_free(&core_fail_info.fail_modules);
obs_find_modules2(load_all_callback, &fail_info);
#ifdef _WIN32
profile_start(reset_win32_symbol_paths_name);
reset_win32_symbol_paths();
profile_end(reset_win32_symbol_paths_name);
#endif
profile_end(obs_load_all_modules2_name);
struct fail_info error = {0};
mfi->count = fail_info.fail_count;
mfi->failed_modules = strlist_split(fail_info.fail_modules.array, ';', false);
dstr_free(&fail_info.fail_modules);
load_core_modules(load_all_callback, &error);
obs->core_modules_loaded = error.fail_count == 0;
dstr_free(&error.fail_modules);
return obs->core_modules_loaded;
}
static void find_modules_in_path(struct obs_module_path *omp, obs_find_module_callback2_t callback, void *param);
void obs_load_plugins(struct obs_runtime_module_info *info, struct obs_module_failure_info *error)
{
struct obs_module_path omp = {.bin = (char *)info->path_info.binary,
.data = (char *)info->path_info.data,
.type = info->type};
struct fail_info failure = {0};
find_modules_in_path(&omp, load_all_callback, &failure);
error->count = failure.fail_count;
error->failed_modules = strlist_split(failure.fail_modules.array, ';', false);
dstr_free(&failure.fail_modules);
}
void obs_module_failure_info_free(struct obs_module_failure_info *mfi)
@@ -610,8 +616,17 @@ void obs_module_failure_info_free(struct obs_module_failure_info *mfi)
}
}
#ifdef _WIN32
static const char *reset_win32_symbol_paths_name = "reset_win32_symbol_paths";
#endif
void obs_post_load_modules(void)
{
#ifdef _WIN32
profile_start(reset_win32_symbol_paths_name);
reset_win32_symbol_paths();
profile_end(reset_win32_symbol_paths_name);
#endif
for (obs_module_t *mod = obs->first_module; !!mod; mod = mod->next)
if (mod->post_load)
mod->post_load();
@@ -667,7 +682,8 @@ bool find_core_module(struct obs_runtime_module_info *info, obs_find_module_call
if (os_file_exists(module_path.array)) {
struct obs_module_info2 callback_info = {.bin_path = module_path.array,
.data_path = parsed_data_directory,
.name = name};
.name = name,
.type = info->type};
callback(data, &callback_info);
found = true;
@@ -686,6 +702,15 @@ bool obs_is_core_module(obs_module_t *module)
return module->module_type == OBS_MODULE_TYPE_CORE;
}
bool obs_is_legacy_module(obs_module_t *module)
{
if (!module) {
return false;
}
return module->module_type == OBS_MODULE_TYPE_LEGACY_PLUGIN;
}
static bool parse_binary_from_directory(struct dstr *parsed_bin_path, const char *bin_path, const char *file)
{
struct dstr directory = {0};
@@ -759,6 +784,7 @@ static void process_found_module(struct obs_module_path *omp, const char *path,
info.bin_path = parsed_bin_path.array;
info.data_path = parsed_data_dir;
info.name = name.array;
info.type = omp->type;
callback(param, &info);
}
@@ -775,7 +801,6 @@ static void find_modules_in_path(struct obs_module_path *omp, obs_find_module_ca
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);
+2 -5
View File
@@ -1266,6 +1266,8 @@ static bool obs_init(const char *locale, const char *module_config_path, profile
obs_register_source(&scene_info);
obs_register_source(&group_info);
obs_register_source(&audio_line_info);
obs->core_modules_loaded = false;
return true;
}
@@ -1461,11 +1463,6 @@ void obs_shutdown(void)
}
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);
+8 -10
View File
@@ -586,16 +586,8 @@ 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);
OBS_DEPRECATED EXPORT void obs_load_all_modules(void);
struct obs_module_failure_info {
char **failed_modules;
@@ -603,7 +595,7 @@ struct obs_module_failure_info {
};
EXPORT void obs_module_failure_info_free(struct obs_module_failure_info *mfi);
EXPORT void obs_load_all_modules2(struct obs_module_failure_info *mfi);
OBS_DEPRECATED EXPORT void obs_load_all_modules2(struct obs_module_failure_info *mfi);
/** Notifies modules that all modules have been loaded. This function should
* be called after all modules have been loaded. */
@@ -623,6 +615,7 @@ struct obs_module_info2 {
const char *bin_path;
const char *data_path;
const char *name;
enum obs_runtime_module_type type;
};
typedef void (*obs_find_module_callback2_t)(void *param, const struct obs_module_info2 *info);
@@ -633,9 +626,14 @@ EXPORT void obs_find_modules2(obs_find_module_callback2_t callback, void *param)
/** Loads all registered core modules. */
EXPORT bool obs_load_core_modules(void);
/** Loads plugins at a given path. omp defines if modern or legacy plugins at path. */
EXPORT void obs_load_plugins(struct obs_runtime_module_info *info, struct obs_module_failure_info *error);
/** Returns true if a module is a core module. */
EXPORT bool obs_is_core_module(obs_module_t *module);
EXPORT bool obs_is_legacy_module(obs_module_t *module);
/** Finds and loads a particular core module.
* Returns false if module cant be found. */
bool find_core_module(struct obs_runtime_module_info *info, obs_find_module_callback2_t callback, void *data);