From d891b40fe36781c4532b5d0bce7ca491579a1fbf Mon Sep 17 00:00:00 2001 From: FiniteSingularity Date: Wed, 17 Jun 2026 14:05:22 -0500 Subject: [PATCH 1/8] cmake: Add explicit target_enable function This provides the counterpart to the existing "target_disable" function, which also allows the functionality to be factored out of existing helper code that might run this implicitly. Co-authored-by: PatTheMav --- cmake/common/helpers_common.cmake | 15 +++++++++++++-- cmake/linux/helpers.cmake | 10 ++++------ cmake/macos/helpers.cmake | 1 - cmake/windows/helpers.cmake | 10 ++++------ plugins/aja-output-ui/CMakeLists.txt | 1 - plugins/aja/CMakeLists.txt | 1 - plugins/coreaudio-encoder/CMakeLists.txt | 1 - plugins/decklink-captions/CMakeLists.txt | 1 - plugins/decklink-output-ui/CMakeLists.txt | 1 - plugins/decklink/CMakeLists.txt | 1 - plugins/linux-alsa/CMakeLists.txt | 1 - plugins/linux-jack/CMakeLists.txt | 1 - plugins/linux-pipewire/CMakeLists.txt | 1 - plugins/linux-pulseaudio/CMakeLists.txt | 1 - plugins/linux-v4l2/CMakeLists.txt | 1 - plugins/mac-syphon/CMakeLists.txt | 1 - plugins/obs-libfdk/CMakeLists.txt | 1 - plugins/obs-nvenc/CMakeLists.txt | 1 - plugins/obs-qsv11/CMakeLists.txt | 1 - plugins/obs-vst/CMakeLists.txt | 1 - plugins/obs-webrtc/CMakeLists.txt | 1 - plugins/oss-audio/CMakeLists.txt | 1 - plugins/sndio/CMakeLists.txt | 1 - plugins/text-freetype2/CMakeLists.txt | 1 - plugins/vlc-video/CMakeLists.txt | 1 - 25 files changed, 21 insertions(+), 36 deletions(-) diff --git a/cmake/common/helpers_common.cmake b/cmake/common/helpers_common.cmake index 4ce77230a09a4b..67e7226f3602b4 100644 --- a/cmake/common/helpers_common.cmake +++ b/cmake/common/helpers_common.cmake @@ -82,6 +82,11 @@ function(target_disable_feature target feature_description) endif() endfunction() +# target_enable: Adds target to list of enabled modules +function(target_enable target) + set_property(GLOBAL APPEND PROPERTY OBS_MODULES_ENABLED ${target}) +endfunction() + # target_disable: Adds target to list of disabled modules function(target_disable target) set_property(GLOBAL APPEND PROPERTY OBS_MODULES_DISABLED ${target}) @@ -499,8 +504,14 @@ function(add_obs_plugin target) if(found_platform AND found_architecture) add_subdirectory(${target}) - elseif(_AOP_WITH_MESSAGE) - add_custom_target(${target} COMMENT "Dummy target for unavailable module ${target}") + endif() + + if(TARGET ${target}) + target_enable(${target}) + else() + if(_AOP_WITH_MESSAGE) + add_custom_target(${target} COMMENT "Dummy target for unavailable module ${target}") + endif() target_disable(${target}) endif() endfunction() diff --git a/cmake/linux/helpers.cmake b/cmake/linux/helpers.cmake index 494586de920808..a13c1a68f32be9 100644 --- a/cmake/linux/helpers.cmake +++ b/cmake/linux/helpers.cmake @@ -213,8 +213,6 @@ function(set_target_properties_obs target) endif() endif() endif() - - set_property(GLOBAL APPEND PROPERTY OBS_MODULES_ENABLED ${target}) endif() target_install_resources(${target}) @@ -236,8 +234,8 @@ function(target_install_resources target) source_group("Resources/${relative_path}" FILES "${data_file}") endforeach() - get_property(obs_module_list GLOBAL PROPERTY OBS_MODULES_ENABLED) - if(target IN_LIST obs_module_list) + get_target_property(target_type ${target} TYPE) + if(target_type STREQUAL MODULE_LIBRARY) set(target_destination "${OBS_DATA_DESTINATION}/obs-plugins/${target}") elseif(target STREQUAL obs) set(target_destination "${OBS_DATA_DESTINATION}/obs-studio") @@ -267,10 +265,10 @@ endfunction() # Helper function to add a specific resource to a bundle function(target_add_resource target resource) - get_property(obs_module_list GLOBAL PROPERTY OBS_MODULES_ENABLED) + get_target_property(target_type ${target} TYPE) if(ARGN) set(target_destination "${ARGN}") - elseif(${target} IN_LIST obs_module_list) + elseif(target_type STREQUAL MODULE_LIBRARY) set(target_destination "${OBS_DATA_DESTINATION}/obs-plugins/${target}") elseif(target STREQUAL obs) set(target_destination "${OBS_DATA_DESTINATION}/obs-studio") diff --git a/cmake/macos/helpers.cmake b/cmake/macos/helpers.cmake index 8913b2b24f9d96..ec4c901708eac2 100644 --- a/cmake/macos/helpers.cmake +++ b/cmake/macos/helpers.cmake @@ -303,7 +303,6 @@ function(set_target_properties_obs target) endif() endif() - set_property(GLOBAL APPEND PROPERTY OBS_MODULES_ENABLED ${target}) set_property(GLOBAL APPEND PROPERTY _OBS_DEPENDENCIES ${target}) endif() diff --git a/cmake/windows/helpers.cmake b/cmake/windows/helpers.cmake index 9a443f1cf75736..806023ab5ede35 100644 --- a/cmake/windows/helpers.cmake +++ b/cmake/windows/helpers.cmake @@ -157,8 +157,6 @@ function(set_target_properties_obs target) endif() endif() endif() - - set_property(GLOBAL APPEND PROPERTY OBS_MODULES_ENABLED ${target}) endif() target_link_options(${target} PRIVATE "/PDBALTPATH:$") @@ -314,8 +312,8 @@ function(target_install_resources target) source_group("Resources/${relative_path}" FILES "${data_file}") endforeach() - get_property(obs_module_list GLOBAL PROPERTY OBS_MODULES_ENABLED) - if(target IN_LIST obs_module_list) + get_target_property(target_type ${target} TYPE) + if(target_type STREQUAL MODULE_LIBRARY) set(target_destination "${OBS_DATA_DESTINATION}/obs-plugins/${target}") elseif(target STREQUAL obs-studio) set(target_destination "${OBS_DATA_DESTINATION}/obs-studio") @@ -346,10 +344,10 @@ endfunction() # Helper function to add a specific resource to a bundle function(target_add_resource target resource) - get_property(obs_module_list GLOBAL PROPERTY OBS_MODULES_ENABLED) + get_target_property(target_type ${target} TYPE) if(ARGN) set(target_destination "${ARGN}") - elseif(${target} IN_LIST obs_module_list) + elseif(target_type STREQUAL MODULE_LIBRARY) set(target_destination "${OBS_DATA_DESTINATION}/obs-plugins/${target}") elseif(target STREQUAL obs-studio) set(target_destination "${OBS_DATA_DESTINATION}/obs-studio") diff --git a/plugins/aja-output-ui/CMakeLists.txt b/plugins/aja-output-ui/CMakeLists.txt index 003490d012364b..56dedbd117f7c6 100644 --- a/plugins/aja-output-ui/CMakeLists.txt +++ b/plugins/aja-output-ui/CMakeLists.txt @@ -1,7 +1,6 @@ cmake_minimum_required(VERSION 3.28...3.30) if(NOT ENABLE_AJA) - target_disable(aja-output-ui) return() endif() diff --git a/plugins/aja/CMakeLists.txt b/plugins/aja/CMakeLists.txt index 88ef18cad3bd43..6b04c1a0b1b9c5 100644 --- a/plugins/aja/CMakeLists.txt +++ b/plugins/aja/CMakeLists.txt @@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.28...3.30) option(ENABLE_AJA "Build OBS with aja support" ON) if(NOT ENABLE_AJA) - set_property(GLOBAL APPEND PROPERTY OBS_MODULES_DISABLED aja) return() endif() diff --git a/plugins/coreaudio-encoder/CMakeLists.txt b/plugins/coreaudio-encoder/CMakeLists.txt index 099995d8919306..907c55348aa629 100644 --- a/plugins/coreaudio-encoder/CMakeLists.txt +++ b/plugins/coreaudio-encoder/CMakeLists.txt @@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.28...3.30) if(OS_WINDOWS) option(ENABLE_COREAUDIO_ENCODER "Enable building with CoreAudio encoder (Windows)" ON) if(NOT ENABLE_COREAUDIO_ENCODER) - target_disable(coreaudio-encoder) return() endif() endif() diff --git a/plugins/decklink-captions/CMakeLists.txt b/plugins/decklink-captions/CMakeLists.txt index 53dab0da14d743..13ab83b7308427 100644 --- a/plugins/decklink-captions/CMakeLists.txt +++ b/plugins/decklink-captions/CMakeLists.txt @@ -1,7 +1,6 @@ cmake_minimum_required(VERSION 3.28...3.30) if(NOT ENABLE_DECKLINK) - target_disable(decklink-captions) return() endif() diff --git a/plugins/decklink-output-ui/CMakeLists.txt b/plugins/decklink-output-ui/CMakeLists.txt index b0cca166cc3fa1..4695c89513a174 100644 --- a/plugins/decklink-output-ui/CMakeLists.txt +++ b/plugins/decklink-output-ui/CMakeLists.txt @@ -1,7 +1,6 @@ cmake_minimum_required(VERSION 3.28...3.30) if(NOT ENABLE_DECKLINK) - target_disable(decklink-output-ui) return() endif() diff --git a/plugins/decklink/CMakeLists.txt b/plugins/decklink/CMakeLists.txt index 18b9b6bc5b6204..bd08406560b1d2 100644 --- a/plugins/decklink/CMakeLists.txt +++ b/plugins/decklink/CMakeLists.txt @@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 3.28...3.30) option(ENABLE_DECKLINK "Build OBS with Decklink support" ON) if(NOT ENABLE_DECKLINK) - target_disable(decklink) return() endif() diff --git a/plugins/linux-alsa/CMakeLists.txt b/plugins/linux-alsa/CMakeLists.txt index 7e7b2f5246ff17..c8d516213eeaa4 100644 --- a/plugins/linux-alsa/CMakeLists.txt +++ b/plugins/linux-alsa/CMakeLists.txt @@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.28...3.30) option(ENABLE_ALSA "Build OBS with ALSA support" ON) if(NOT ENABLE_ALSA) - target_disable(linux-alsa) return() endif() diff --git a/plugins/linux-jack/CMakeLists.txt b/plugins/linux-jack/CMakeLists.txt index 4e58530a78b479..1a3e61f616fab7 100644 --- a/plugins/linux-jack/CMakeLists.txt +++ b/plugins/linux-jack/CMakeLists.txt @@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 3.28...3.30) option(ENABLE_JACK "Build OBS with JACK support" OFF) if(NOT ENABLE_JACK) - target_disable(linux-jack) return() endif() diff --git a/plugins/linux-pipewire/CMakeLists.txt b/plugins/linux-pipewire/CMakeLists.txt index e81bdfe2f2c262..baf367df2c880b 100644 --- a/plugins/linux-pipewire/CMakeLists.txt +++ b/plugins/linux-pipewire/CMakeLists.txt @@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 3.28...3.30) option(ENABLE_PIPEWIRE "Enable PipeWire support" ON) if(NOT ENABLE_PIPEWIRE) - target_disable(linux-pipewire) return() endif() diff --git a/plugins/linux-pulseaudio/CMakeLists.txt b/plugins/linux-pulseaudio/CMakeLists.txt index 5ab582c163854a..d45cc5a070c2a7 100644 --- a/plugins/linux-pulseaudio/CMakeLists.txt +++ b/plugins/linux-pulseaudio/CMakeLists.txt @@ -1,7 +1,6 @@ cmake_minimum_required(VERSION 3.28...3.30) if(NOT ENABLE_PULSEAUDIO) - target_disable(linux-pulseaudio) return() endif() diff --git a/plugins/linux-v4l2/CMakeLists.txt b/plugins/linux-v4l2/CMakeLists.txt index aafbc0d28b0e36..43dbd2eb2b8a38 100644 --- a/plugins/linux-v4l2/CMakeLists.txt +++ b/plugins/linux-v4l2/CMakeLists.txt @@ -4,7 +4,6 @@ option(ENABLE_V4L2 "Build OBS with v4l2 support" ON) option(ENABLE_UDEV "Build linux-v4l2 with UDEV support" ON) if(NOT ENABLE_V4L2) - target_disable(linux-v4l2) return() endif() diff --git a/plugins/mac-syphon/CMakeLists.txt b/plugins/mac-syphon/CMakeLists.txt index a0d48604216d05..8d411c87b30d87 100644 --- a/plugins/mac-syphon/CMakeLists.txt +++ b/plugins/mac-syphon/CMakeLists.txt @@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 3.28...3.30) option(ENABLE_SYPHON "Enable Syphon sharing support" ON) if(NOT ENABLE_SYPHON) - target_disable(mac-syphon) target_disable_feature(mac-syphon "Syphon sharing support") return() else() diff --git a/plugins/obs-libfdk/CMakeLists.txt b/plugins/obs-libfdk/CMakeLists.txt index c785a08db18c55..c0b722aa653d0d 100644 --- a/plugins/obs-libfdk/CMakeLists.txt +++ b/plugins/obs-libfdk/CMakeLists.txt @@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 3.28...3.30) option(ENABLE_LIBFDK "Enable FDK AAC support" OFF) if(NOT ENABLE_LIBFDK) - target_disable(obs-libfdk) return() endif() diff --git a/plugins/obs-nvenc/CMakeLists.txt b/plugins/obs-nvenc/CMakeLists.txt index 93053c1edb9e13..1efeca52280047 100644 --- a/plugins/obs-nvenc/CMakeLists.txt +++ b/plugins/obs-nvenc/CMakeLists.txt @@ -6,7 +6,6 @@ mark_as_advanced(ENABLE_NVENC_FFMPEG_IDS) if(NOT ENABLE_NVENC) target_disable_feature(obs-nvenc "NVIDIA Hardware Encoder") - target_disable(obs-nvenc) return() endif() diff --git a/plugins/obs-qsv11/CMakeLists.txt b/plugins/obs-qsv11/CMakeLists.txt index 85b150f6d2ece4..9d06d4304e2bf1 100644 --- a/plugins/obs-qsv11/CMakeLists.txt +++ b/plugins/obs-qsv11/CMakeLists.txt @@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.28...3.30) option(ENABLE_QSV11 "Build Intel QSV11 Hardware Encoder." TRUE) if(NOT ENABLE_QSV11) target_disable_feature(obs-qsv11 "Intel QSV11 Hardware Encoder") - target_disable(obs-qsv11) return() endif() diff --git a/plugins/obs-vst/CMakeLists.txt b/plugins/obs-vst/CMakeLists.txt index 8a2b4eb7938733..3bc8c75403cf1b 100644 --- a/plugins/obs-vst/CMakeLists.txt +++ b/plugins/obs-vst/CMakeLists.txt @@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.28...3.30) option(ENABLE_VST "Enable building OBS with VST plugin" ON) if(NOT ENABLE_VST) - target_disable(obs-vst) return() endif() diff --git a/plugins/obs-webrtc/CMakeLists.txt b/plugins/obs-webrtc/CMakeLists.txt index d57ca8af5297b5..fcc53a7119bdbc 100644 --- a/plugins/obs-webrtc/CMakeLists.txt +++ b/plugins/obs-webrtc/CMakeLists.txt @@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 3.28...3.30) option(ENABLE_WEBRTC "Enable WebRTC Output support" ON) if(NOT ENABLE_WEBRTC) - target_disable(obs-webrtc) return() endif() diff --git a/plugins/oss-audio/CMakeLists.txt b/plugins/oss-audio/CMakeLists.txt index 5ae20e2cc7394a..b08a2bf51a384a 100644 --- a/plugins/oss-audio/CMakeLists.txt +++ b/plugins/oss-audio/CMakeLists.txt @@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.28...3.30) option(ENABLE_OSS "Enable building with OSS audio support" ON) if(NOT ENABLE_OSS) - target_disable(oss-audio) return() endif() diff --git a/plugins/sndio/CMakeLists.txt b/plugins/sndio/CMakeLists.txt index f76e7c07e1f385..008e54bb2721c2 100644 --- a/plugins/sndio/CMakeLists.txt +++ b/plugins/sndio/CMakeLists.txt @@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 3.28...3.30) option(ENABLE_SNDIO "Build OBS with sndio support" OFF) if(NOT ENABLE_SNDIO) - target_disable(sndio) return() endif() diff --git a/plugins/text-freetype2/CMakeLists.txt b/plugins/text-freetype2/CMakeLists.txt index 8643dd0b7bb6e9..955cb73577a689 100644 --- a/plugins/text-freetype2/CMakeLists.txt +++ b/plugins/text-freetype2/CMakeLists.txt @@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.28...3.30) option(ENABLE_FREETYPE "Enable FreeType text plugin" ON) if(NOT ENABLE_FREETYPE) - target_disable(text-freetype2) return() endif() diff --git a/plugins/vlc-video/CMakeLists.txt b/plugins/vlc-video/CMakeLists.txt index 871036c9f52b38..0b6ecbec16d150 100644 --- a/plugins/vlc-video/CMakeLists.txt +++ b/plugins/vlc-video/CMakeLists.txt @@ -23,7 +23,6 @@ endmacro() option(ENABLE_VLC "Build OBS with VLC plugin support" ON) if(NOT ENABLE_VLC) - target_disable(vlc-video) return() endif() From e2e19f857c9a86207d89b0df1ec7376feb4f667c Mon Sep 17 00:00:00 2001 From: FiniteSingularity Date: Wed, 17 Jun 2026 14:11:25 -0500 Subject: [PATCH 2/8] cmake: Add concept of "core" modules Core modules are main OBS Studio functionality implemented as runtime modules, but are conceptually considered mandatory and the application is incomplete without them. This change also introduces a configure-time dependency of core modules on "libobs" (already a generation-time dependency) and produces a library target with the names of all core modules available at build system generation. Co-authored-by: PatTheMav --- cmake/common/helpers_common.cmake | 33 +++++++++- libobs/CMakeLists.txt | 2 + libobs/cmake/obs-core-modules.cmake | 8 +++ libobs/obs-core-modules.c.in | 17 +++++ libobs/obs-core-modules.h | 8 +++ plugins/CMakeLists.txt | 97 ++++++++++++++++------------- 6 files changed, 117 insertions(+), 48 deletions(-) create mode 100644 libobs/cmake/obs-core-modules.cmake create mode 100644 libobs/obs-core-modules.c.in create mode 100644 libobs/obs-core-modules.h diff --git a/cmake/common/helpers_common.cmake b/cmake/common/helpers_common.cmake index 67e7226f3602b4..9e37012ef556f6 100644 --- a/cmake/common/helpers_common.cmake +++ b/cmake/common/helpers_common.cmake @@ -40,7 +40,7 @@ function(message_configuration) endforeach() endif() - if(ENABLE_PLUGINS) + if(ENABLE_CORE_MODULES) get_property(OBS_MODULES_ENABLED GLOBAL PROPERTY OBS_MODULES_ENABLED) list(SORT OBS_MODULES_ENABLED COMPARE NATURAL CASE SENSITIVE ORDER ASCENDING) @@ -456,13 +456,17 @@ function(check_uuid uuid_string return_value) return(PROPAGATE ${return_value}) endfunction() -# add_obs_plugin: Add plugin subdirectory if host platform is in specified list of supported platforms and architectures -function(add_obs_plugin target) +# add_core_module: Add module subdirectory if host platform is in specified list of supported platforms and architectures +function(add_core_module target) set(options WITH_MESSAGE) set(oneValueArgs "") set(multiValueArgs PLATFORMS ARCHITECTURES) cmake_parse_arguments(PARSE_ARGV 0 _AOP "${options}" "${oneValueArgs}" "${multiValueArgs}") + if(NOT TARGET libobs) + message(FATAL_ERROR "Cannot add libobs core module without existing 'libobs' target") + endif() + set(found_platform FALSE) list(LENGTH _AOP_PLATFORMS _AOP_NUM_PLATFORMS) @@ -515,3 +519,26 @@ function(add_obs_plugin target) target_disable(${target}) endif() endfunction() + +function(set_obs_core_modules) + if(NOT TARGET libobs OR NOT TARGET libobs-core-modules) + message(FATAL_ERROR "Unable to set up OBS Core Modules without 'libobs' target") + endif() + + # get_target_property(core_modules_list libobs CORE_MODULE_TARGETS) + get_property(OBS_MODULES_ENABLED GLOBAL PROPERTY OBS_MODULES_ENABLED) + get_target_property(libobs_source_directory libobs SOURCE_DIR) + get_target_property(libobs_binary_directory libobs BINARY_DIR) + + list(LENGTH OBS_MODULES_ENABLED core_modules_count) + string(REPLACE ";" "\",\n\t\"" core_modules_array_content "${OBS_MODULES_ENABLED}") + + set(OBS_CORE_MODULE_COUNT "${core_modules_count}") + set(OBS_CORE_MODULE_LIST "\t\"${core_modules_array_content}\"") + + configure_file( + "${libobs_source_directory}/obs-core-modules.c.in" + "${libobs_binary_directory}/obs-core-modules.c" + @ONLY + ) +endfunction() diff --git a/libobs/CMakeLists.txt b/libobs/CMakeLists.txt index b20c2fa476ce5c..6e7e8ade97a341 100644 --- a/libobs/CMakeLists.txt +++ b/libobs/CMakeLists.txt @@ -7,6 +7,8 @@ if(OS_WINDOWS AND NOT OBS_PARENT_ARCHITECTURE STREQUAL CMAKE_VS_PLATFORM_NAME) return() endif() +include(cmake/obs-core-modules.cmake) + find_package(SIMDe REQUIRED) find_package(Threads REQUIRED) diff --git a/libobs/cmake/obs-core-modules.cmake b/libobs/cmake/obs-core-modules.cmake new file mode 100644 index 00000000000000..b9b600c249c71d --- /dev/null +++ b/libobs/cmake/obs-core-modules.cmake @@ -0,0 +1,8 @@ +add_library(libobs-core-modules OBJECT) +add_library(OBS::libobs-core-modules ALIAS libobs-core-modules) + +target_sources(libobs-core-modules PRIVATE obs-core-modules.c obs-core-modules.h) + +target_include_directories(libobs-core-modules PUBLIC "$") + +set_property(TARGET libobs-core-modules PROPERTY FOLDER core) diff --git a/libobs/obs-core-modules.c.in b/libobs/obs-core-modules.c.in new file mode 100644 index 00000000000000..8ee5221525c4f2 --- /dev/null +++ b/libobs/obs-core-modules.c.in @@ -0,0 +1,17 @@ +#include +#include + +const char *obs_core_modules[] = { +@OBS_CORE_MODULE_LIST@ +}; + +const unsigned int obs_core_modules_count = @OBS_CORE_MODULE_COUNT@; + +bool obs_in_core_module_list(const char *name) { + for (unsigned int i = 0; i < obs_core_modules_count; i++) { + if(strcmp(obs_core_modules[i], name) == 0) { + return true; + } + } + return false; +} diff --git a/libobs/obs-core-modules.h b/libobs/obs-core-modules.h new file mode 100644 index 00000000000000..297a6eaf6f6fb6 --- /dev/null +++ b/libobs/obs-core-modules.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +extern const char *obs_core_modules[]; +extern const unsigned int obs_core_modules_count; + +extern bool obs_in_core_module_list(const char *name); diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 1bea2e4ff92d02..6bc682accd5c3c 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -1,20 +1,25 @@ cmake_minimum_required(VERSION 3.28...3.30) -option(ENABLE_PLUGINS "Enable building OBS plugins" ON) +if(NOT TARGET libobs) + message(FATAL_ERROR "OBS Core modules require 'libobs' target.") +endif() + +option(ENABLE_CORE_MODULES "Enable building OBS Core Modules" ON) +set_property(GLOBAL PROPERTY ENABLE_PLUGINS ${ENABLE_CORE_MODULES}) -if(NOT ENABLE_PLUGINS) - set_property(GLOBAL APPEND PROPERTY OBS_FEATURES_DISABLED "Plugin Support") +if(NOT ENABLE_CORE_MODULES) + set_property(GLOBAL APPEND PROPERTY OBS_FEATURES_DISABLED "Core Modules") return() endif() -set_property(GLOBAL APPEND PROPERTY OBS_FEATURES_ENABLED "Plugin Support") +set_property(GLOBAL APPEND PROPERTY OBS_FEATURES_ENABLED "Core Modules") macro(check_obs_browser) if((OS_WINDOWS AND CMAKE_VS_PLATFORM_NAME MATCHES "(ARM64|x64)") OR OS_MACOS OR OS_LINUX) if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/obs-browser/CMakeLists.txt") message(FATAL_ERROR "Required submodule 'obs-browser' not available.") else() - add_subdirectory(obs-browser) + add_core_module(obs-browser) endif() else() add_custom_target(obs-browser) @@ -26,81 +31,83 @@ macro(check_obs_websocket) if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/obs-websocket/CMakeLists.txt") message(FATAL_ERROR "Required submodule 'obs-websocket' not available.") else() - add_subdirectory(obs-websocket) + add_core_module(obs-websocket) endif() endmacro() # Add plugins in alphabetical order to retain order in IDE projects -add_obs_plugin( +add_core_module( aja PLATFORMS WINDOWS MACOS LINUX WITH_MESSAGE ) -add_obs_plugin(aja-output-ui PLATFORMS WINDOWS MACOS LINUX WITH_MESSAGE) -add_obs_plugin(coreaudio-encoder PLATFORMS WINDOWS MACOS) -add_obs_plugin( +add_core_module(aja-output-ui PLATFORMS WINDOWS MACOS LINUX WITH_MESSAGE) +add_core_module(coreaudio-encoder PLATFORMS WINDOWS MACOS) +add_core_module( decklink PLATFORMS WINDOWS MACOS LINUX WITH_MESSAGE ) -add_obs_plugin( +add_core_module( decklink-captions PLATFORMS WINDOWS MACOS LINUX WITH_MESSAGE ) -add_obs_plugin( +add_core_module( decklink-output-ui PLATFORMS WINDOWS MACOS LINUX WITH_MESSAGE ) -add_obs_plugin( +add_core_module( frontend-tools PLATFORMS WINDOWS MACOS LINUX WITH_MESSAGE ) -add_obs_plugin(image-source) -add_obs_plugin(linux-alsa PLATFORMS LINUX FREEBSD OPENBSD) -add_obs_plugin(linux-capture PLATFORMS LINUX FREEBSD OPENBSD) -add_obs_plugin(linux-jack PLATFORMS LINUX FREEBSD OPENBSD) -add_obs_plugin(linux-pipewire PLATFORMS LINUX FREEBSD OPENBSD) -add_obs_plugin(linux-pulseaudio PLATFORMS LINUX FREEBSD OPENBSD) -add_obs_plugin(linux-v4l2 PLATFORMS LINUX FREEBSD OPENBSD) -add_obs_plugin(mac-avcapture PLATFORMS MACOS) -add_obs_plugin(mac-capture PLATFORMS MACOS) -add_obs_plugin(mac-syphon PLATFORMS MACOS) -add_obs_plugin(mac-videotoolbox PLATFORMS MACOS) -add_obs_plugin(mac-virtualcam PLATFORMS MACOS) -add_obs_plugin(nv-filters PLATFORMS WINDOWS) +add_core_module(image-source) +add_core_module(linux-alsa PLATFORMS LINUX FREEBSD OPENBSD) +add_core_module(linux-capture PLATFORMS LINUX FREEBSD OPENBSD) +add_core_module(linux-jack PLATFORMS LINUX FREEBSD OPENBSD) +add_core_module(linux-pipewire PLATFORMS LINUX FREEBSD OPENBSD) +add_core_module(linux-pulseaudio PLATFORMS LINUX FREEBSD OPENBSD) +add_core_module(linux-v4l2 PLATFORMS LINUX FREEBSD OPENBSD) +add_core_module(mac-avcapture PLATFORMS MACOS) +add_core_module(mac-capture PLATFORMS MACOS) +add_core_module(mac-syphon PLATFORMS MACOS) +add_core_module(mac-videotoolbox PLATFORMS MACOS) +add_core_module(mac-virtualcam PLATFORMS MACOS) +add_core_module(nv-filters PLATFORMS WINDOWS) check_obs_browser() -add_obs_plugin(obs-ffmpeg) -add_obs_plugin(obs-filters) -add_obs_plugin(obs-libfdk) -add_obs_plugin(obs-nvenc PLATFORMS WINDOWS LINUX ARCHITECTURES x64 x86_64) -add_obs_plugin(obs-outputs) -add_obs_plugin( +add_core_module(obs-ffmpeg) +add_core_module(obs-filters) +add_core_module(obs-libfdk) +add_core_module(obs-nvenc PLATFORMS WINDOWS LINUX ARCHITECTURES x64 x86_64) +add_core_module(obs-outputs) +add_core_module( obs-qsv11 PLATFORMS WINDOWS LINUX ARCHITECTURES x64 x86_64 ) -add_obs_plugin(obs-text PLATFORMS WINDOWS) -add_obs_plugin(obs-transitions) -add_obs_plugin( +add_core_module(obs-text PLATFORMS WINDOWS) +add_core_module(obs-transitions) +add_core_module( obs-vst PLATFORMS WINDOWS MACOS LINUX WITH_MESSAGE ) -add_obs_plugin(obs-webrtc) +add_core_module(obs-webrtc) check_obs_websocket() -add_obs_plugin(obs-x264) -add_obs_plugin(oss-audio PLATFORMS FREEBSD OPENBSD) -add_obs_plugin(rtmp-services) -add_obs_plugin(sndio PLATFORMS LINUX FREEBSD OPENBSD) -add_obs_plugin(text-freetype2) -add_obs_plugin(vlc-video WITH_MESSAGE) -add_obs_plugin(win-capture PLATFORMS WINDOWS) -add_obs_plugin(win-dshow PLATFORMS WINDOWS) -add_obs_plugin(win-wasapi PLATFORMS WINDOWS) +add_core_module(obs-x264) +add_core_module(oss-audio PLATFORMS FREEBSD OPENBSD) +add_core_module(rtmp-services) +add_core_module(sndio PLATFORMS LINUX FREEBSD OPENBSD) +add_core_module(text-freetype2) +add_core_module(vlc-video WITH_MESSAGE) +add_core_module(win-capture PLATFORMS WINDOWS) +add_core_module(win-dshow PLATFORMS WINDOWS) +add_core_module(win-wasapi PLATFORMS WINDOWS) + +set_obs_core_modules() From 0403d6894ee49230428685d0565f3dca8ad62b1c Mon Sep 17 00:00:00 2001 From: FiniteSingularity Date: Wed, 17 Jun 2026 14:20:07 -0500 Subject: [PATCH 3/8] cmake: Change name of core module output directory Moves first-party runtime modules into the "core" directory rather than the "obs-plugins" directory to better differentiate them from legacy plugins as well as existing 3rd-party plugins. This change also requires the existing plugin directory to be explicitly added as a possible module search path by application logic, as libobs's internal logic will not add this path anymore and thus third-party plugins would not be discovered anymore. Co-authored-by: PatTheMav --- cmake/linux/defaults.cmake | 4 +-- cmake/linux/helpers.cmake | 25 +++++++++++-------- cmake/windows/defaults.cmake | 4 +-- cmake/windows/helpers.cmake | 16 ++++++------ frontend/updater/init-hook-files.c | 2 +- frontend/updater/updater.cpp | 4 +-- frontend/widgets/OBSBasic.cpp | 25 +++++++++++++++++++ libobs/obs-nix.c | 10 ++++---- libobs/obs-windows.c | 4 +-- plugins/win-capture/game-capture-file-init.c | 2 +- .../virtualcam-module/CMakeLists.txt | 4 +-- .../virtualcam-install.bat.in | 8 +++--- .../virtualcam-uninstall.bat.in | 8 +++--- 13 files changed, 73 insertions(+), 43 deletions(-) diff --git a/cmake/linux/defaults.cmake b/cmake/linux/defaults.cmake index b6a0061ff30cb8..5ffe864e6b77f2 100644 --- a/cmake/linux/defaults.cmake +++ b/cmake/linux/defaults.cmake @@ -25,13 +25,13 @@ set(OBS_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/rundir") set(OBS_EXECUTABLE_DESTINATION "${CMAKE_INSTALL_BINDIR}") set(OBS_INCLUDE_DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/obs") set(OBS_LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}") -set(OBS_PLUGIN_DESTINATION "${CMAKE_INSTALL_LIBDIR}/obs-plugins") +set(OBS_PLUGIN_DESTINATION "${CMAKE_INSTALL_LIBDIR}/obs-modules") set(OBS_SCRIPT_PLUGIN_DESTINATION "${CMAKE_INSTALL_LIBDIR}/obs-scripting") set(OBS_DATA_DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/obs") set(OBS_CMAKE_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake") # Set additional paths used by OBS for self-discovery -set(OBS_PLUGIN_PATH "${CMAKE_INSTALL_LIBDIR}/obs-plugins") +set(OBS_PLUGIN_PATH "${CMAKE_INSTALL_LIBDIR}/obs-modules/core") set(OBS_SCRIPT_PLUGIN_PATH "${CMAKE_INSTALL_LIBDIR}/obs-scripting") set(OBS_DATA_PATH "${OBS_DATA_DESTINATION}") set(OBS_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") diff --git a/cmake/linux/helpers.cmake b/cmake/linux/helpers.cmake index a13c1a68f32be9..c8b78097fe3618 100644 --- a/cmake/linux/helpers.cmake +++ b/cmake/linux/helpers.cmake @@ -22,6 +22,10 @@ function(set_target_properties_obs target) set(OBS_SOVERSION 30) if(target_type STREQUAL EXECUTABLE) + if(target STREQUAL browser-helper) + set(OBS_EXECUTABLE_DESTINATION "${OBS_PLUGIN_DESTINATION}/core") + endif() + install(TARGETS ${target} RUNTIME DESTINATION "${OBS_EXECUTABLE_DESTINATION}" COMPONENT Runtime) add_custom_command( @@ -124,7 +128,7 @@ function(set_target_properties_obs target) set(plugin_destination "${OBS_SCRIPT_PLUGIN_DESTINATION}") set_property(TARGET ${target} PROPERTY INSTALL_RPATH "$ORIGIN/;$ORIGIN/..") else() - set(plugin_destination "${OBS_PLUGIN_DESTINATION}") + set(plugin_destination "${OBS_PLUGIN_DESTINATION}/core") endif() install( @@ -170,20 +174,21 @@ function(set_target_properties_obs target) add_custom_command( TARGET ${target} POST_BUILD - COMMAND "${CMAKE_COMMAND}" -E make_directory "${OBS_OUTPUT_DIR}/$/${OBS_PLUGIN_DESTINATION}/" + COMMAND "${CMAKE_COMMAND}" -E make_directory "${OBS_OUTPUT_DIR}/$/${OBS_PLUGIN_DESTINATION}/core" COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${imported_location}" "${cef_location}/chrome-sandbox" "${cef_location}/libEGL.so" "${cef_location}/libGLESv2.so" "${cef_location}/libvk_swiftshader.so" "${cef_location}/libvulkan.so.1" "${cef_location}/v8_context_snapshot.bin" - "${cef_location}/vk_swiftshader_icd.json" "${OBS_OUTPUT_DIR}/$/${OBS_PLUGIN_DESTINATION}/" + "${cef_location}/vk_swiftshader_icd.json" "${OBS_OUTPUT_DIR}/$/${OBS_PLUGIN_DESTINATION}/core" COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${cef_root_location}/Resources/chrome_100_percent.pak" "${cef_root_location}/Resources/chrome_200_percent.pak" "${cef_root_location}/Resources/icudtl.dat" - "${cef_root_location}/Resources/resources.pak" "${OBS_OUTPUT_DIR}/$/${OBS_PLUGIN_DESTINATION}/" + "${cef_root_location}/Resources/resources.pak" + "${OBS_OUTPUT_DIR}/$/${OBS_PLUGIN_DESTINATION}/core" COMMAND "${CMAKE_COMMAND}" -E copy_directory "${cef_root_location}/Resources/locales" - "${OBS_OUTPUT_DIR}/$/${OBS_PLUGIN_DESTINATION}/locales" - COMMENT "Add Chromium Embedded Framework to library directory" + "${OBS_OUTPUT_DIR}/$/${OBS_PLUGIN_DESTINATION}/core/locales" + COMMENT "Add Chromium Embedded Framwork to library directory" ) install( @@ -200,13 +205,13 @@ function(set_target_properties_obs target) "${cef_root_location}/Resources/chrome_200_percent.pak" "${cef_root_location}/Resources/icudtl.dat" "${cef_root_location}/Resources/resources.pak" - DESTINATION "${OBS_PLUGIN_DESTINATION}" + DESTINATION "${OBS_PLUGIN_DESTINATION}/core" COMPONENT Runtime ) install( DIRECTORY "${cef_root_location}/Resources/locales" - DESTINATION "${OBS_PLUGIN_DESTINATION}" + DESTINATION "${OBS_PLUGIN_DESTINATION}/core" USE_SOURCE_PERMISSIONS COMPONENT Runtime ) @@ -236,7 +241,7 @@ function(target_install_resources target) get_target_property(target_type ${target} TYPE) if(target_type STREQUAL MODULE_LIBRARY) - set(target_destination "${OBS_DATA_DESTINATION}/obs-plugins/${target}") + set(target_destination "${OBS_DATA_DESTINATION}/obs-modules/core/${target}") elseif(target STREQUAL obs) set(target_destination "${OBS_DATA_DESTINATION}/obs-studio") else() @@ -269,7 +274,7 @@ function(target_add_resource target resource) if(ARGN) set(target_destination "${ARGN}") elseif(target_type STREQUAL MODULE_LIBRARY) - set(target_destination "${OBS_DATA_DESTINATION}/obs-plugins/${target}") + set(target_destination "${OBS_DATA_DESTINATION}/obs-modules/core/${target}") elseif(target STREQUAL obs) set(target_destination "${OBS_DATA_DESTINATION}/obs-studio") else() diff --git a/cmake/windows/defaults.cmake b/cmake/windows/defaults.cmake index c2e141105ce6bf..e212ac66e4ec2b 100644 --- a/cmake/windows/defaults.cmake +++ b/cmake/windows/defaults.cmake @@ -5,7 +5,7 @@ include_guard(GLOBAL) set(OBS_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") set(OBS_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/rundir") -set(OBS_PLUGIN_DESTINATION obs-plugins/64bit) +set(OBS_PLUGIN_DESTINATION core) set(OBS_DATA_DESTINATION data) set(OBS_CMAKE_DESTINATION cmake) set(OBS_SCRIPT_PLUGIN_DESTINATION "${OBS_DATA_DESTINATION}/obs-scripting/64bit") @@ -14,7 +14,7 @@ set(OBS_EXECUTABLE_DESTINATION bin/64bit) set(OBS_LIBRARY_DESTINATION lib) set(OBS_INCLUDE_DESTINATION include) # Set relative paths used by OBS for self-discovery -set(OBS_PLUGIN_PATH "../../${CMAKE_INSTALL_LIBDIR}/obs-plugins/64bit") +set(OBS_PLUGIN_PATH "../../${CMAKE_INSTALL_LIBDIR}/core") set(OBS_SCRIPT_PLUGIN_PATH "../../${OBS_DATA_DESTINATION}/obs-scripting/64bit") set(OBS_DATA_PATH "../../${OBS_DATA_DESTINATION}") diff --git a/cmake/windows/helpers.cmake b/cmake/windows/helpers.cmake index 806023ab5ede35..5e597666f9a67d 100644 --- a/cmake/windows/helpers.cmake +++ b/cmake/windows/helpers.cmake @@ -22,9 +22,9 @@ function(set_target_properties_obs target) if(target_type STREQUAL EXECUTABLE) if(target STREQUAL obs-browser-helper) - set(OBS_EXECUTABLE_DESTINATION "${OBS_PLUGIN_DESTINATION}") + set(OBS_EXECUTABLE_DESTINATION "${OBS_PLUGIN_DESTINATION}/obs-browser") elseif(target STREQUAL inject-helper OR target STREQUAL get-graphics-offsets) - set(OBS_EXECUTABLE_DESTINATION "${OBS_DATA_DESTINATION}/obs-plugins/win-capture") + set(OBS_EXECUTABLE_DESTINATION "${OBS_PLUGIN_DESTINATION}/win-capture/data") _target_install_obs(${target} DESTINATION ${OBS_EXECUTABLE_DESTINATION} x86) @@ -66,7 +66,7 @@ function(set_target_properties_obs target) elseif(target STREQUAL "obspython" OR target STREQUAL "obslua") set(target_destination "${OBS_SCRIPT_PLUGIN_DESTINATION}") elseif(target STREQUAL graphics-hook) - set(target_destination "${OBS_DATA_DESTINATION}/obs-plugins/win-capture") + set(target_destination "${OBS_PLUGIN_DESTINATION}/win-capture/data") target_add_resource(graphics-hook "${CMAKE_CURRENT_SOURCE_DIR}/obs-vulkan64.json" "${target_destination}") target_add_resource(graphics-hook "${CMAKE_CURRENT_SOURCE_DIR}/obs-vulkan32.json" "${target_destination}") @@ -76,7 +76,7 @@ function(set_target_properties_obs target) _target_install_obs(${target} DESTINATION ${target_destination} x64) endif() elseif(target STREQUAL obs-virtualcam-module) - set(target_destination "${OBS_DATA_DESTINATION}/obs-plugins/win-dshow") + set(target_destination "${OBS_PLUGIN_DESTINATION}/win-dshow/data") _target_install_obs(${target} DESTINATION ${target_destination} x86) @@ -84,7 +84,7 @@ function(set_target_properties_obs target) _target_install_obs(${target} DESTINATION ${target_destination} x64) endif() else() - set(target_destination "${OBS_PLUGIN_DESTINATION}") + set(target_destination "${OBS_PLUGIN_DESTINATION}/${target}") endif() _target_install_obs(${target} DESTINATION ${target_destination}) @@ -314,7 +314,7 @@ function(target_install_resources target) get_target_property(target_type ${target} TYPE) if(target_type STREQUAL MODULE_LIBRARY) - set(target_destination "${OBS_DATA_DESTINATION}/obs-plugins/${target}") + set(target_destination "${OBS_PLUGIN_DESTINATION}/${target}/data") elseif(target STREQUAL obs-studio) set(target_destination "${OBS_DATA_DESTINATION}/obs-studio") else() @@ -348,7 +348,7 @@ function(target_add_resource target resource) if(ARGN) set(target_destination "${ARGN}") elseif(target_type STREQUAL MODULE_LIBRARY) - set(target_destination "${OBS_DATA_DESTINATION}/obs-plugins/${target}") + set(target_destination "${OBS_PLUGIN_DESTINATION}/${target}/data") elseif(target STREQUAL obs-studio) set(target_destination "${OBS_DATA_DESTINATION}/obs-studio") else() @@ -395,7 +395,7 @@ function(_bundle_dependencies target) set(plugins_list) foreach(library IN LISTS found_dependencies) - # CEF needs to be placed in obs-plugins directory on Windows, which is handled already + # CEF needs to be placed in core directory on Windows, which is handled already if(${library} STREQUAL CEF::Library) continue() endif() diff --git a/frontend/updater/init-hook-files.c b/frontend/updater/init-hook-files.c index a5df3c767bf67a..6f2e867a0a357d 100644 --- a/frontend/updater/init-hook-files.c +++ b/frontend/updater/init-hook-files.c @@ -106,7 +106,7 @@ static LSTATUS get_reg(HKEY hkey, LPCWSTR sub_key, LPCWSTR value_name, bool b64) } while (false) #define IMPLICIT_LAYERS L"SOFTWARE\\Khronos\\Vulkan\\ImplicitLayers" -#define HOOK_LOCATION L"\\data\\obs-plugins\\win-capture\\" +#define HOOK_LOCATION L"\\core\\win-capture\\data\\" static bool update_hook_file(bool b64) { diff --git a/frontend/updater/updater.cpp b/frontend/updater/updater.cpp index fcb1248b2ddd6f..bbe9f7f63a136f 100644 --- a/frontend/updater/updater.cpp +++ b/frontend/updater/updater.cpp @@ -741,7 +741,7 @@ static inline bool FileExists(const wchar_t *path) static bool NonCorePackageInstalled(const char *name) { if (strcmp(name, "obs-browser") == 0) { - return FileExists(L"obs-plugins\\64bit\\obs-browser.dll"); + return FileExists(L"core\\obs-browser\\obs-browser.dll"); } return false; @@ -1744,7 +1744,7 @@ static bool Update(wchar_t *cmdLine) StringCbCat(regsvr, sizeof(regsvr), L"\\regsvr32.exe"); StringCbCopy(src, sizeof(src), obs_base_directory); - StringCbCat(src, sizeof(src), L"\\data\\obs-plugins\\win-dshow\\"); + StringCbCat(src, sizeof(src), L"\\core\\win-dshow\\data\\"); StringCbCopy(tmp, sizeof(tmp), L"\""); StringCbCat(tmp, sizeof(tmp), regsvr); diff --git a/frontend/widgets/OBSBasic.cpp b/frontend/widgets/OBSBasic.cpp index b6f5445305f301..e8ddd25fc1c739 100644 --- a/frontend/widgets/OBSBasic.cpp +++ b/frontend/widgets/OBSBasic.cpp @@ -140,6 +140,31 @@ static void AddExtraModulePaths() #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; } diff --git a/libobs/obs-nix.c b/libobs/obs-nix.c index 65cd1251dcfe06..ccc8575e5279b4 100644 --- a/libobs/obs-nix.c +++ b/libobs/obs-nix.c @@ -51,14 +51,14 @@ const char *get_module_extension(void) static const char *module_bin[] = { "../../obs-plugins/64bit", - OBS_INSTALL_PREFIX "/" OBS_PLUGIN_DESTINATION, - FLATPAK_PLUGIN_PATH "/" OBS_PLUGIN_DESTINATION, + OBS_INSTALL_PREFIX "/" OBS_PLUGIN_DESTINATION "/core", + FLATPAK_PLUGIN_PATH "/" OBS_PLUGIN_DESTINATION "/core", }; static const char *module_data[] = { OBS_DATA_PATH "/obs-plugins/%module%", - OBS_INSTALL_DATA_PATH "/obs-plugins/%module%", - FLATPAK_PLUGIN_PATH "/share/obs/obs-plugins/%module%", + OBS_INSTALL_DATA_PATH "/obs-modules/core/%module%", + FLATPAK_PLUGIN_PATH "/share/obs/obs-modules/core/%module%", }; static const int module_patterns_size = sizeof(module_bin) / sizeof(module_bin[0]); @@ -68,7 +68,7 @@ static const struct obs_nix_hotkeys_vtable *hotkeys_vtable = NULL; void add_default_module_paths(void) { char *module_bin_path = os_get_executable_path_ptr("../" OBS_PLUGIN_PATH); - char *module_data_path = os_get_executable_path_ptr("../" OBS_DATA_PATH "/obs-plugins/%module%"); + char *module_data_path = os_get_executable_path_ptr("../" OBS_DATA_PATH "/obs-modules/core/%module%"); if (module_bin_path && module_data_path) { char *abs_module_bin_path = os_get_abs_path_ptr(module_bin_path); diff --git a/libobs/obs-windows.c b/libobs/obs-windows.c index 90fb343052eea2..3871e7c8326a32 100644 --- a/libobs/obs-windows.c +++ b/libobs/obs-windows.c @@ -34,9 +34,9 @@ const char *get_module_extension(void) return ".dll"; } -static const char *module_bin[] = {"../../obs-plugins/64bit"}; +static const char *module_bin[] = {"../../core/%module%"}; -static const char *module_data[] = {"../../data/obs-plugins/%module%"}; +static const char *module_data[] = {"../../core/%module%/data"}; static const int module_patterns_size = sizeof(module_bin) / sizeof(module_bin[0]); diff --git a/plugins/win-capture/game-capture-file-init.c b/plugins/win-capture/game-capture-file-init.c index fdfa95e63be2aa..0c8dab7dafebe8 100644 --- a/plugins/win-capture/game-capture-file-init.c +++ b/plugins/win-capture/game-capture-file-init.c @@ -177,7 +177,7 @@ static bool update_hook_file(bool b64) wchar_t dst_json[MAX_PATH]; StringCbCopyW(temp, sizeof(temp), - L"..\\..\\data\\obs-plugins\\" + L"..\\..\\data\\core\\" L"win-capture\\"); make_filename(temp, L"obs-vulkan", L".json"); diff --git a/plugins/win-dshow/virtualcam-module/CMakeLists.txt b/plugins/win-dshow/virtualcam-module/CMakeLists.txt index 8ac916e17a9a5d..87cda18fa4eb57 100644 --- a/plugins/win-dshow/virtualcam-module/CMakeLists.txt +++ b/plugins/win-dshow/virtualcam-module/CMakeLists.txt @@ -138,12 +138,12 @@ target_sources(obs-virtualcam-module PRIVATE cmake/windows/${_OUTPUT_NAME}.def) configure_file(virtualcam-install.bat.in virtualcam-install.bat) target_add_resource(obs-virtualcam-module "${CMAKE_CURRENT_BINARY_DIR}/virtualcam-install.bat" - "${OBS_DATA_DESTINATION}/obs-plugins/win-dshow" + "${OBS_PLUGIN_DESTINATION}/win-dshow/data" ) configure_file(virtualcam-uninstall.bat.in virtualcam-uninstall.bat) target_add_resource(obs-virtualcam-module "${CMAKE_CURRENT_BINARY_DIR}/virtualcam-uninstall.bat" - "${OBS_DATA_DESTINATION}/obs-plugins/win-dshow" + "${OBS_PLUGIN_DESTINATION}/win-dshow/data" ) set_target_properties_obs( diff --git a/plugins/win-dshow/virtualcam-module/virtualcam-install.bat.in b/plugins/win-dshow/virtualcam-module/virtualcam-install.bat.in index f28696e8b41902..51166e8b37b33d 100644 --- a/plugins/win-dshow/virtualcam-module/virtualcam-install.bat.in +++ b/plugins/win-dshow/virtualcam-module/virtualcam-install.bat.in @@ -36,8 +36,8 @@ goto checkAdmin :install32DLL echo Installing 32-bit Virtual Cam... - if exist "%~dp0\data\obs-plugins\win-dshow\obs-virtualcam-module32.dll" ( - regsvr32.exe /i /s "%~dp0\data\obs-plugins\win-dshow\obs-virtualcam-module32.dll" + if exist "%~dp0\data\core\win-dshow\obs-virtualcam-module32.dll" ( + regsvr32.exe /i /s "%~dp0\data\core\win-dshow\obs-virtualcam-module32.dll" ) else ( regsvr32.exe /i /s obs-virtualcam-module32.dll ) @@ -54,8 +54,8 @@ goto checkAdmin :install64DLL echo Installing 64-bit Virtual Cam... - if exist "%~dp0\data\obs-plugins\win-dshow\obs-virtualcam-module64.dll" ( - regsvr32.exe /i /s "%~dp0\data\obs-plugins\win-dshow\obs-virtualcam-module64.dll" + if exist "%~dp0\data\core\win-dshow\obs-virtualcam-module64.dll" ( + regsvr32.exe /i /s "%~dp0\data\core\win-dshow\obs-virtualcam-module64.dll" ) else ( regsvr32.exe /i /s obs-virtualcam-module64.dll ) diff --git a/plugins/win-dshow/virtualcam-module/virtualcam-uninstall.bat.in b/plugins/win-dshow/virtualcam-module/virtualcam-uninstall.bat.in index aefebf1b852b28..fb9e417d038fca 100644 --- a/plugins/win-dshow/virtualcam-module/virtualcam-uninstall.bat.in +++ b/plugins/win-dshow/virtualcam-module/virtualcam-uninstall.bat.in @@ -12,13 +12,13 @@ goto checkAdmin ) :uninstallDLLs - if exist "%~dp0\data\obs-plugins\win-dshow\obs-virtualcam-module32.dll" ( - regsvr32.exe /u /s "%~dp0\data\obs-plugins\win-dshow\obs-virtualcam-module32.dll" + if exist "%~dp0\data\core\win-dshow\obs-virtualcam-module32.dll" ( + regsvr32.exe /u /s "%~dp0\data\core\win-dshow\obs-virtualcam-module32.dll" ) else ( regsvr32.exe /u /s obs-virtualcam-module32.dll ) - if exist "%~dp0\data\obs-plugins\win-dshow\obs-virtualcam-module64.dll" ( - regsvr32.exe /u /s "%~dp0\data\obs-plugins\win-dshow\obs-virtualcam-module64.dll" + if exist "%~dp0\data\core\win-dshow\obs-virtualcam-module64.dll" ( + regsvr32.exe /u /s "%~dp0\data\core\win-dshow\obs-virtualcam-module64.dll" ) else ( regsvr32.exe /u /s obs-virtualcam-module64.dll ) From a531afd3d0dc1fa4681df8943870ebcbbb261276 Mon Sep 17 00:00:00 2001 From: FiniteSingularity Date: Wed, 17 Jun 2026 14:23:20 -0500 Subject: [PATCH 4/8] libobs: Add core module loading functionality Adds a separate code path just for loading core modules based on the list of modules created at build system generation. Paths to load core modules from is platform-specific, but not changeable for users and is thus not changeable by a caller. Instead path discovery is implemented for each platform separately, generating the necessary module loading package directly before handing it over to the existing module load callback function. Co-authored-by: PatTheMav --- libobs/CMakeLists.txt | 5 +++ libobs/obs-cocoa.m | 9 ---- libobs/obs-core-modules-loader-mac.m | 43 +++++++++++++++++++ libobs/obs-core-modules-loader-nix.c | 43 +++++++++++++++++++ libobs/obs-core-modules-loader-win.c | 47 ++++++++++++++++++++ libobs/obs-core-modules-loader.h | 5 +++ libobs/obs-internal.h | 2 + libobs/obs-module.c | 64 ++++++++++++++++++++++++++-- libobs/obs-nix.c | 41 ------------------ libobs/obs-windows.c | 12 ------ libobs/obs.c | 2 - libobs/obs.h | 24 +++++++++++ 12 files changed, 230 insertions(+), 67 deletions(-) create mode 100644 libobs/obs-core-modules-loader-mac.m create mode 100644 libobs/obs-core-modules-loader-nix.c create mode 100644 libobs/obs-core-modules-loader-win.c create mode 100644 libobs/obs-core-modules-loader.h diff --git a/libobs/CMakeLists.txt b/libobs/CMakeLists.txt index 6e7e8ade97a341..acceaba87d4c39 100644 --- a/libobs/CMakeLists.txt +++ b/libobs/CMakeLists.txt @@ -39,6 +39,10 @@ target_sources( obs-avc.h obs-canvas.c obs-config.h + $<$:obs-core-modules-loader-mac.m> + $<$:obs-core-modules-loader-win.c> + $<$:obs-core-modules-loader-nix.c> + obs-core-modules-loader.h obs-data.c obs-data.h obs-defs.h @@ -243,6 +247,7 @@ target_link_libraries( PRIVATE OBS::caption OBS::libobs-version + OBS::libobs-core-modules FFmpeg::avcodec FFmpeg::avformat FFmpeg::avutil diff --git a/libobs/obs-cocoa.m b/libobs/obs-cocoa.m index 08b64ef1b0aa01..6acb1f1629dad4 100644 --- a/libobs/obs-cocoa.m +++ b/libobs/obs-cocoa.m @@ -35,15 +35,6 @@ return ""; } -void add_default_module_paths(void) -{ - NSURL *pluginURL = [[NSBundle mainBundle] builtInPlugInsURL]; - NSString *pluginModulePath = [[pluginURL path] stringByAppendingString:@"/%module%.plugin/Contents/MacOS/"]; - NSString *pluginDataPath = [[pluginURL path] stringByAppendingString:@"/%module%.plugin/Contents/Resources/"]; - - obs_add_module_path(pluginModulePath.UTF8String, pluginDataPath.UTF8String); -} - char *find_libobs_data_file(const char *file) { NSBundle *frameworkBundle = [NSBundle bundleWithIdentifier:@"com.obsproject.libobs"]; diff --git a/libobs/obs-core-modules-loader-mac.m b/libobs/obs-core-modules-loader-mac.m new file mode 100644 index 00000000000000..3813c264bd6acc --- /dev/null +++ b/libobs/obs-core-modules-loader-mac.m @@ -0,0 +1,43 @@ +#import "obs-core-modules.h" + +#import +#import +#import +#import + +#import + +extern bool find_core_module(struct obs_runtime_module_info *info, obs_find_module_callback2_t callback, void *data); + +void load_core_modules(obs_find_module_callback2_t callback, void *data) +{ + NSURL *pluginURL = [[NSBundle mainBundle] builtInPlugInsURL]; + NSString *pluginBasePath = [pluginURL path]; + + for (unsigned int i = 0; i < obs_core_modules_count; i++) { + const char *name = obs_core_modules[i]; + NSString *moduleName = [NSString stringWithUTF8String:name]; + + NSString *binPath = + [NSString stringWithFormat:@"%@/%@.plugin/Contents/MacOS/%@", pluginBasePath, moduleName, moduleName]; + + NSString *dataPath = + [NSString stringWithFormat:@"%@/%@.plugin/Contents/Resources/", pluginBasePath, moduleName]; + + if (![[NSFileManager defaultManager] fileExistsAtPath:binPath]) { + blog(LOG_ERROR, "Core Module %s required but missing!", name); + return; + } + + struct obs_runtime_module_info module_info = { + .path_info = {.binary = [binPath UTF8String], .data = [dataPath UTF8String]}, + .type = MODULE_TYPE_CORE, + .name = name + }; + + if (!find_core_module(&module_info, callback, data)) { + blog(LOG_ERROR, "Failed to load core module %s", name); + return; + } + } +} diff --git a/libobs/obs-core-modules-loader-nix.c b/libobs/obs-core-modules-loader-nix.c new file mode 100644 index 00000000000000..5352be4fc429f4 --- /dev/null +++ b/libobs/obs-core-modules-loader-nix.c @@ -0,0 +1,43 @@ +#include "obs-core-modules.h" + +#include +#include +#include +#include + +const char *core_module_bin = "../" OBS_PLUGIN_PATH "/%module%"; +const char *core_module_data = "../" OBS_DATA_PATH "/obs-modules/core/%module%"; + +extern bool find_core_module(struct obs_runtime_module_info *info, obs_find_module_callback2_t callback, void *data); + +void load_core_modules(obs_find_module_callback2_t callback, void *data) +{ + char *core_bin_path = os_get_executable_path_ptr(core_module_bin); + char *core_data_path = os_get_executable_path_ptr(core_module_data); + + for (unsigned int i = 0; i < obs_core_modules_count; i++) { + const char *name = obs_core_modules[i]; + struct dstr bin_path = {0}; + struct dstr data_path = {0}; + dstr_init_copy(&bin_path, core_bin_path); + dstr_init_copy(&data_path, core_data_path); + dstr_replace(&bin_path, "%module%", name); + dstr_replace(&data_path, "%module%", name); + + struct obs_runtime_module_info module_info = {.path_info = {.binary = bin_path.array, + .data = data_path.array}, + .type = MODULE_TYPE_CORE, + .name = name}; + + if (!find_core_module(&module_info, callback, data)) { + blog(LOG_ERROR, "Failed to load core module %s", name); + return; + } + + dstr_free(&bin_path); + dstr_free(&data_path); + } + + bfree(core_bin_path); + bfree(core_data_path); +} diff --git a/libobs/obs-core-modules-loader-win.c b/libobs/obs-core-modules-loader-win.c new file mode 100644 index 00000000000000..302aed8fe18754 --- /dev/null +++ b/libobs/obs-core-modules-loader-win.c @@ -0,0 +1,47 @@ +#include "obs-core-modules.h" + +#include +#include +#include +#include + +const char *core_module_bin = "../../core/%module%/%module%"; +const char *core_module_data = "../../core/%module%/data"; + +extern bool find_core_module(struct obs_runtime_module_info *info, obs_find_module_callback2_t callback, void *data); + +void load_core_modules(obs_find_module_callback2_t callback, void *data) +{ + char *core_bin_path = os_get_abs_path_ptr(core_module_bin); + char *core_data_path = os_get_abs_path_ptr(core_module_data); + + for (unsigned int i = 0; i < obs_core_modules_count; i++) { + const char *name = obs_core_modules[i]; + struct dstr bin_path = {0}; + struct dstr data_path = {0}; + dstr_init_copy(&bin_path, core_bin_path); + dstr_init_copy(&data_path, core_data_path); + dstr_replace(&bin_path, "%module%", name); + dstr_replace(&data_path, "%module%", name); + + // Convert windows backslash to forward slash + dstr_replace(&bin_path, "\\", "/"); + dstr_replace(&data_path, "\\", "/"); + + struct obs_runtime_module_info module_info = {.path_info = {.binary = bin_path.array, + .data = data_path.array}, + .type = MODULE_TYPE_CORE, + .name = name}; + + if (!find_core_module(&module_info, callback, data)) { + blog(LOG_ERROR, "Failed to load core module %s", name); + return; + } + + dstr_free(&bin_path); + dstr_free(&data_path); + } + + bfree(core_bin_path); + bfree(core_data_path); +} diff --git a/libobs/obs-core-modules-loader.h b/libobs/obs-core-modules-loader.h new file mode 100644 index 00000000000000..66a231269fc7e9 --- /dev/null +++ b/libobs/obs-core-modules-loader.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern void load_core_modules(obs_find_module_callback2_t callback, void *data); diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h index ca5bdac88afdf5..8898243eed22da 100644 --- a/libobs/obs-internal.h +++ b/libobs/obs-internal.h @@ -135,6 +135,8 @@ struct obs_module { DARRAY(char *) outputs; DARRAY(char *) encoders; DARRAY(char *) services; + + enum obs_runtime_module_type module_type; }; struct obs_disabled_module { diff --git a/libobs/obs-module.c b/libobs/obs-module.c index d623607be67d16..9e415e41cecfeb 100644 --- a/libobs/obs-module.c +++ b/libobs/obs-module.c @@ -15,12 +15,13 @@ along with this program. If not, see . ******************************************************************************/ -#include "util/platform.h" -#include "util/dstr.h" - +#include "obs-core-modules-loader.h" +#include "obs-core-modules.h" #include "obs-defs.h" #include "obs-internal.h" #include "obs-module.h" +#include "util/dstr.h" +#include "util/platform.h" extern const char *get_module_extension(void); @@ -581,6 +582,17 @@ void obs_load_all_modules2(struct obs_module_failure_info *mfi) memset(mfi, 0, sizeof(*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); + } + dstr_free(&core_fail_info.fail_modules); + obs_find_modules2(load_all_callback, &fail_info); #ifdef _WIN32 profile_start(reset_win32_symbol_paths_name); @@ -632,6 +644,52 @@ static char *make_data_directory(const char *module_name, const char *data_dir) return parsed_data_dir.array; } +/* Function to find a specifically defined module. Used for loading core modules by name. + * omp->bin should be path to the module binary, without its file extension. + * omp->data should be path to the module data directory (including the module name) + */ +bool find_core_module(struct obs_runtime_module_info *info, obs_find_module_callback2_t callback, void *data) +{ + const char *file_name = strrchr(info->path_info.binary, '/'); + const char *name = file_name ? (file_name + 1) : info->path_info.binary; + info->name = name; + + if (info->type != MODULE_TYPE_CORE) { + blog(LOG_WARNING, "Module %s is not a core module, and must be loaded with the plugin loader.", name); + return false; + } + + bool found = false; + + struct dstr module_path = {0}; + dstr_copy(&module_path, info->path_info.binary); + dstr_cat(&module_path, get_module_extension()); + + char *parsed_data_directory; + parsed_data_directory = make_data_directory(name, info->path_info.data); + + 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}; + + callback(data, &callback_info); + found = true; + } + + bfree(parsed_data_directory); + dstr_free(&module_path); + + return found; +} + +bool obs_is_core_module(obs_module_t *module) +{ + if (!module) + return false; + return module->module_type == MODULE_TYPE_CORE; +} + static bool parse_binary_from_directory(struct dstr *parsed_bin_path, const char *bin_path, const char *file) { struct dstr directory = {0}; diff --git a/libobs/obs-nix.c b/libobs/obs-nix.c index ccc8575e5279b4..6c38516821e4c9 100644 --- a/libobs/obs-nix.c +++ b/libobs/obs-nix.c @@ -47,49 +47,8 @@ const char *get_module_extension(void) return ".so"; } -#define FLATPAK_PLUGIN_PATH "/app/plugins" - -static const char *module_bin[] = { - "../../obs-plugins/64bit", - OBS_INSTALL_PREFIX "/" OBS_PLUGIN_DESTINATION "/core", - FLATPAK_PLUGIN_PATH "/" OBS_PLUGIN_DESTINATION "/core", -}; - -static const char *module_data[] = { - OBS_DATA_PATH "/obs-plugins/%module%", - OBS_INSTALL_DATA_PATH "/obs-modules/core/%module%", - FLATPAK_PLUGIN_PATH "/share/obs/obs-modules/core/%module%", -}; - -static const int module_patterns_size = sizeof(module_bin) / sizeof(module_bin[0]); - static const struct obs_nix_hotkeys_vtable *hotkeys_vtable = NULL; -void add_default_module_paths(void) -{ - char *module_bin_path = os_get_executable_path_ptr("../" OBS_PLUGIN_PATH); - char *module_data_path = os_get_executable_path_ptr("../" OBS_DATA_PATH "/obs-modules/core/%module%"); - - if (module_bin_path && module_data_path) { - char *abs_module_bin_path = os_get_abs_path_ptr(module_bin_path); - char *abs_module_install_path = os_get_abs_path_ptr(OBS_INSTALL_PREFIX "/" OBS_PLUGIN_DESTINATION); - - if (abs_module_bin_path && - (!abs_module_install_path || strcmp(abs_module_bin_path, abs_module_install_path) != 0)) { - obs_add_module_path(module_bin_path, module_data_path); - } - bfree(abs_module_install_path); - bfree(abs_module_bin_path); - } - - bfree(module_bin_path); - bfree(module_data_path); - - for (int i = 0; i < module_patterns_size; i++) { - obs_add_module_path(module_bin[i], module_data[i]); - } -} - /* * /usr/local/share/libobs * /usr/share/libobs diff --git a/libobs/obs-windows.c b/libobs/obs-windows.c index 3871e7c8326a32..f74c5c62cacd4d 100644 --- a/libobs/obs-windows.c +++ b/libobs/obs-windows.c @@ -34,18 +34,6 @@ const char *get_module_extension(void) return ".dll"; } -static const char *module_bin[] = {"../../core/%module%"}; - -static const char *module_data[] = {"../../core/%module%/data"}; - -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]/data/libobs */ char *find_libobs_data_file(const char *file) { diff --git a/libobs/obs.c b/libobs/obs.c index ba53eecef92836..c23fd33277d92d 100644 --- a/libobs/obs.c +++ b/libobs/obs.c @@ -27,7 +27,6 @@ struct obs_core *obs = NULL; static THREAD_LOCAL bool is_ui_thread = false; -extern void add_default_module_paths(void); extern char *find_libobs_data_file(const char *file); static inline void make_video_info(struct video_output_info *vi, struct obs_video_info *ovi) @@ -1261,7 +1260,6 @@ 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); - add_default_module_paths(); return true; } diff --git a/libobs/obs.h b/libobs/obs.h index 4e51eb4488a0ca..6a4b76ce8527da 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -173,6 +173,19 @@ enum obs_module_load_state { OBS_MODULE_FAILED_TO_INITIALIZE, }; +enum obs_runtime_module_type { MODULE_TYPE_UNDEFINED, MODULE_TYPE_CORE, MODULE_TYPE_PLUGIN, MODULE_TYPE_LEGACY_PLUGIN }; + +struct obs_runtime_module_path { + const char *binary; + const char *data; +}; + +struct obs_runtime_module_info { + struct obs_runtime_module_path path_info; + enum obs_runtime_module_type type; + const char *name; +}; + struct obs_transform_info { struct vec2 pos; float rot; @@ -609,6 +622,17 @@ typedef void (*obs_find_module_callback2_t)(void *param, const struct obs_module /** Finds all modules within the search paths added by obs_add_module_path. */ EXPORT void obs_find_modules2(obs_find_module_callback2_t callback, void *param); + +/** Loads all registered core modules. */ +EXPORT bool obs_load_core_modules(); + +/** Returns true if a module is a core module. */ +EXPORT bool obs_is_core_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); + #endif typedef void (*obs_enum_module_callback_t)(void *param, obs_module_t *module); From 5e2d908071e7fa623b661c63dfe627fb3814161c Mon Sep 17 00:00:00 2001 From: FiniteSingularity Date: Wed, 17 Jun 2026 14:25:52 -0500 Subject: [PATCH 5/8] 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. * 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. * 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. Co-authored-by: PatTheMav --- frontend/CMakeLists.txt | 4 - frontend/OBSApp.cpp | 58 +++- frontend/OBSApp.hpp | 3 +- frontend/cmake/feature-plugin-manager.cmake | 21 ++ frontend/data/locale/en-US.ini | 6 + .../plugin-manager/LoaderPaths_BSD.hpp.in | 13 + .../plugin-manager/LoaderPaths_Linux.hpp.in | 13 + frontend/plugin-manager/PluginManager.cpp | 82 +++++- frontend/plugin-manager/PluginManager.hpp | 37 ++- .../plugin-manager/PluginManagerWindow.cpp | 55 +++- .../plugin-manager/PluginManagerWindow.hpp | 7 +- .../plugin-manager/PluginModuleLoader.hpp | 66 +++++ .../plugin-manager/PluginModuleLoader_BSD.cpp | 219 ++++++++++++++++ .../PluginModuleLoader_Linux.cpp | 247 ++++++++++++++++++ .../PluginModuleLoader_MacOS.mm | 195 ++++++++++++++ .../PluginModuleLoader_Windows.cpp | 193 ++++++++++++++ frontend/widgets/OBSBasic.cpp | 176 +------------ libobs/obs-internal.h | 2 + libobs/obs-module.c | 82 +++--- libobs/obs.c | 2 + libobs/obs.h | 9 +- 21 files changed, 1256 insertions(+), 234 deletions(-) create mode 100644 frontend/plugin-manager/LoaderPaths_BSD.hpp.in create mode 100644 frontend/plugin-manager/LoaderPaths_Linux.hpp.in create mode 100644 frontend/plugin-manager/PluginModuleLoader.hpp create mode 100644 frontend/plugin-manager/PluginModuleLoader_BSD.cpp create mode 100644 frontend/plugin-manager/PluginModuleLoader_Linux.cpp create mode 100644 frontend/plugin-manager/PluginModuleLoader_MacOS.mm create mode 100644 frontend/plugin-manager/PluginModuleLoader_Windows.cpp diff --git a/frontend/CMakeLists.txt b/frontend/CMakeLists.txt index f0cde6b47b855e..f74c7a7b704c58 100644 --- a/frontend/CMakeLists.txt +++ b/frontend/CMakeLists.txt @@ -94,10 +94,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}) diff --git a/frontend/OBSApp.cpp b/frontend/OBSApp.cpp index 0d6d0e28e35fbd..ebd30ca6795958 100644 --- a/frontend/OBSApp.cpp +++ b/frontend/OBSApp.cpp @@ -88,6 +88,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; @@ -2037,16 +2066,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() +{ + 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() { - 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 PluginState = OBS::PluginManager::State; + PluginState loadState = pluginManager_->loadState(); + + if (loadState != PluginState::Success) { + PluginFailureAction action = handlePluginFailure(); + + if (action == PluginFailureAction::OpenPluginManager) { + pluginManagerOpenDialog(); + } + } } void OBSApp::pluginManagerOpenDialog() diff --git a/frontend/OBSApp.hpp b/frontend/OBSApp.hpp index fc5774e047398e..e3e26fb6b5f9cd 100644 --- a/frontend/OBSApp.hpp +++ b/frontend/OBSApp.hpp @@ -236,12 +236,13 @@ private slots: 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 Exec(VoidFunc func); diff --git a/frontend/cmake/feature-plugin-manager.cmake b/frontend/cmake/feature-plugin-manager.cmake index faf9cf2a0f0879..949c3b02082f6d 100644 --- a/frontend/cmake/feature-plugin-manager.cmake +++ b/frontend/cmake/feature-plugin-manager.cmake @@ -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,19 @@ 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_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) diff --git a/frontend/data/locale/en-US.ini b/frontend/data/locale/en-US.ini index 51ea30b4aa1186..e92bd5ba09a398 100644 --- a/frontend/data/locale/en-US.ini +++ b/frontend/data/locale/en-US.ini @@ -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 3rd party plugins.\n\nOpen Plugin Manager to check the state of 3rd party plugins?" +PluginFailure.Dialog.Continue="Continue" +PluginFailure.Dialog.Open="Open Pugin 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?" diff --git a/frontend/plugin-manager/LoaderPaths_BSD.hpp.in b/frontend/plugin-manager/LoaderPaths_BSD.hpp.in new file mode 100644 index 00000000000000..018409af15bdbb --- /dev/null +++ b/frontend/plugin-manager/LoaderPaths_BSD.hpp.in @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace OBS { + namespace 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@"}; + } +} diff --git a/frontend/plugin-manager/LoaderPaths_Linux.hpp.in b/frontend/plugin-manager/LoaderPaths_Linux.hpp.in new file mode 100644 index 00000000000000..018409af15bdbb --- /dev/null +++ b/frontend/plugin-manager/LoaderPaths_Linux.hpp.in @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace OBS { + namespace 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@"}; + } +} diff --git a/frontend/plugin-manager/PluginManager.cpp b/frontend/plugin-manager/PluginManager.cpp index 5cd351d453bdc2..8e6a51b57e2103 100644 --- a/frontend/plugin-manager/PluginManager.cpp +++ b/frontend/plugin-manager/PluginManager.cpp @@ -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,33 @@ 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."; + return; + } + + 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 +117,7 @@ std::filesystem::path PluginManager::getConfigFilePath_() return path; } -void PluginManager::loadModules_() +void PluginManager::loadModuleConfiguration_() { auto modulesFile = getConfigFilePath_(); if (std::filesystem::exists(modulesFile)) { @@ -282,7 +309,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 +342,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); +} diff --git a/frontend/plugin-manager/PluginManager.hpp b/frontend/plugin-manager/PluginManager.hpp index 2f6d5808ae9f7d..c07cf54c3ce274 100644 --- a/frontend/plugin-manager/PluginManager.hpp +++ b/frontend/plugin-manager/PluginManager.hpp @@ -43,22 +43,41 @@ struct ModuleInfo { }; class PluginManager { +public: + using ModuleList = std::vector; + enum class Mode { CoreOnly, Full }; + enum class State { Failure, PartialFailure, Success }; + private: + Mode loadMode_{Mode::Full}; + State loadState_{State::Failure}; + std::vector modules_ = {}; - std::vector disabledSources_ = {}; - std::vector disabledOutputs_ = {}; - std::vector disabledServices_ = {}; - std::vector 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 { diff --git a/frontend/plugin-manager/PluginManagerWindow.cpp b/frontend/plugin-manager/PluginManagerWindow.cpp index b29cb12c89f72f..0cb90a8c215958 100644 --- a/frontend/plugin-manager/PluginManagerWindow.cpp +++ b/frontend/plugin-manager/PluginManagerWindow.cpp @@ -33,7 +33,9 @@ extern bool safe_mode; namespace OBS { -PluginManagerWindow::PluginManagerWindow(std::vector const &modules, QWidget *parent) +PluginManagerWindow::PluginManagerWindow(std::vector const &modules, + std::vector const &failedModules, QWidget *parent) + : QDialog(parent), modules_(modules), ui(new Ui::PluginManagerWindow) @@ -61,6 +63,9 @@ PluginManagerWindow::PluginManagerWindow(std::vector 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,35 @@ PluginManagerWindow::PluginManagerWindow(std::vector 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 = nullptr; + + 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 +134,19 @@ PluginManagerWindow::PluginManagerWindow(std::vector const &modules, row++; } + QLabel *item = new QLabel("FAILED ITEMS"); + item->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + ui->modulesList->layout()->addWidget(item); + + for (std::string const &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(ui->modulesList->layout()); if (safe_mode) { QLabel *safeModeLabel = new QLabel(ui->modulesList); @@ -175,4 +206,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 diff --git a/frontend/plugin-manager/PluginManagerWindow.hpp b/frontend/plugin-manager/PluginManagerWindow.hpp index 4c851067b4b7a6..54e19e4a9670e3 100644 --- a/frontend/plugin-manager/PluginManagerWindow.hpp +++ b/frontend/plugin-manager/PluginManagerWindow.hpp @@ -30,9 +30,14 @@ class PluginManagerWindow : public QDialog { std::unique_ptr ui; public: - explicit PluginManagerWindow(std::vector const &modules, QWidget *parent = nullptr); + enum class Page { Installed, Failure }; + + explicit PluginManagerWindow(std::vector const &modules, + std::vector const &failedModules, QWidget *parent = nullptr); inline std::vector const result() { return modules_; } + void setPage(Page page); + private: std::vector modules_; diff --git a/frontend/plugin-manager/PluginModuleLoader.hpp b/frontend/plugin-manager/PluginModuleLoader.hpp new file mode 100644 index 00000000000000..3865912940e3c9 --- /dev/null +++ b/frontend/plugin-manager/PluginModuleLoader.hpp @@ -0,0 +1,66 @@ +/****************************************************************************** + Copyright (C) 2026 by FiniteSingularity + + 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 . +******************************************************************************/ + +#pragma once + +#include + +#include +#include +#include +#include + +using FailureInfo = obs_module_failure_info; +using ModuleLoadInfo = obs_runtime_module_info; +using ModuleList = std::vector; + +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 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(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(result.count); +} diff --git a/frontend/plugin-manager/PluginModuleLoader_BSD.cpp b/frontend/plugin-manager/PluginModuleLoader_BSD.cpp new file mode 100644 index 00000000000000..94a1c6f6e73272 --- /dev/null +++ b/frontend/plugin-manager/PluginModuleLoader_BSD.cpp @@ -0,0 +1,219 @@ +/****************************************************************************** + Copyright (C) 2026 by FiniteSingularity + + 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 . +******************************************************************************/ + +#include "PluginModuleLoader.hpp" +#include "PluginManager.hpp" +#include + +#include +#include +#include + +#include +#include +#include +#include + +// BSD 3rd-Party Plugin Locations +// +// * User Path: +// * Binary: //.so +// * Data: //data +// +// * System Root Path: /usr +// * Binary: //obs-modules/plugins/.so +// * Data: /share/obs/obs-modules/plugins/ +// +// * User Root Path: /home//.config +// * Binary: /obs-studio/plugins//.so +// * Data: /obs-studio/plugins//data +// +// * Legacy User Path: + +// * Binary: /.so +// * Data: / +// +// * Legacy System Root Path: /usr +// * Binary: //obs-plugins/.so +// * Data: /share/obs/obs-plugins/ +// +// * Legacy User Root Path: /home//.config +// * Binary: /obs-studio/plugins//bin/64bit/.so +// * Data: /obs-studio/plugins//data +// +// * Legacy Fallback Path: +// * Binary: /../../obs-plugins/64bit/.so +// * Data: /share/obs/obs-plugins/ +// + +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 kUserBinaryPath{"/%module%"}; +constexpr std::string_view kUserDataPath{"/%module%/data"}; +constexpr std::string_view kLegacyConfigBinaryPath{"/obs-studio/plugins/%module%/bin/64bit"}; +constexpr std::string_view kLegacyConfigDataPath{"/obs-studio/plugins/%module%/data"}; + +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 = MODULE_TYPE_PLUGIN, + .name = nullptr}; + + userPluginState = pluginLoadHelper(info, failedModules_); + } else { + userPluginState = State::Success; + } + + State systemPluginState = State::Failure; + + if (Constants::kPlatformPluginModulePath.size() && Constants::kPlatformPluginDataPath.size()) { + 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 = 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 = 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 = 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 = 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 = MODULE_TYPE_LEGACY_PLUGIN, + .name = nullptr}; + + configPluginState = pluginLoadHelper(info, failedModules_); + } else { + configPluginState = State::Success; + } + } + + return (userPluginState && systemPluginState && configPluginState) ? State::Success : State::PartialFailure; +} +} // namespace OBS diff --git a/frontend/plugin-manager/PluginModuleLoader_Linux.cpp b/frontend/plugin-manager/PluginModuleLoader_Linux.cpp new file mode 100644 index 00000000000000..9f214ffdca4a9b --- /dev/null +++ b/frontend/plugin-manager/PluginModuleLoader_Linux.cpp @@ -0,0 +1,247 @@ +/****************************************************************************** + Copyright (C) 2026 by FiniteSingularity + + 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 . +******************************************************************************/ + +#include "PluginModuleLoader.hpp" +#include "PluginManager.hpp" +#include + +#include +#include +#include + +#include +#include +#include +#include + +// Linux 3rd-Party Plugin Locations +// +// * User Path: +// * Binary: //.so +// * Data: //data +// +// * System Root Path: /usr +// * Binary: //obs-modules/plugins/.so +// * Data: /share/obs/obs-modules/plugins/ +// +// * User Root Path: /home//.config +// * Binary: /obs-studio/plugins//.so +// * Data: /obs-studio/plugins//data +// +// * Legacy User Path: + +// * Binary: /.so +// * Data: / +// +// * Legacy System Root Path: /usr +// * Binary: //obs-plugins/.so +// * Data: /share/obs/obs-plugins/ +// +// * Legacy User Root Path: /home//.config +// * Binary: /obs-studio/plugins//bin/64bit/.so +// * Data: /obs-studio/plugins//data +// +// * Legacy Fallback Path: +// * Binary: /../../obs-plugins/64bit/.so +// * Data: /share/obs/obs-plugins/ +// +// * Legacy Flatpak Root Path: /app/plugins +// * Binary: //obs-plugins/.so +// * Data: /share/obs/obs-plugins/ +// + +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 kUserBinaryPath{"/%module%"}; +constexpr std::string_view kUserDataPath{"/%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 kLegacyFlatpakPath{"/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 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 = MODULE_TYPE_PLUGIN, + .name = nullptr}; + + userPluginState = pluginLoadHelper(info, failedModules_); + } else { + userPluginState = State::Success; + } + + State systemPluginState = State::Failure; + + if (Constants::kPlatformPluginModulePath.size() && Constants::kPlatformPluginDataPath.size()) { + 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 = 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 = 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 = 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 = 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 = MODULE_TYPE_LEGACY_PLUGIN, + .name = nullptr}; + + configPluginState = pluginLoadHelper(info, failedModules_); + } else { + configPluginState = State::Success; + } + } + + State flatpakPluginState = State::Failure; + + { + std::string binaryPath{kLegacyFlatpakPath}; + std::string dataPath{kLegacyFlatpakPath}; + 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 = MODULE_TYPE_LEGACY_PLUGIN, + .name = nullptr}; + + flatpakPluginState = pluginLoadHelper(info, failedModules_); + } + + return (userPluginState && systemPluginState && configPluginState && flatpakPluginState) + ? State::Success + : State::PartialFailure; +} +} // namespace OBS diff --git a/frontend/plugin-manager/PluginModuleLoader_MacOS.mm b/frontend/plugin-manager/PluginModuleLoader_MacOS.mm new file mode 100644 index 00000000000000..3b0c514ed78267 --- /dev/null +++ b/frontend/plugin-manager/PluginModuleLoader_MacOS.mm @@ -0,0 +1,195 @@ +/****************************************************************************** + Copyright (C) 2026 by FiniteSingularity + + 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 . +******************************************************************************/ + +#include "PluginModuleLoader.hpp" +#include "PluginManager.hpp" + +#include +#include +#include + +#include +#include +#include + +// macOS 3rd-Party Plugin Locations +// +// * User Path: +// * Binary: /.plugin/Contents/MacOS +// * Data: /.plugin/Contents/Resources +// +// * Root Path: /Library/Application Support +// * Binary: /obs-studio/plugins/.plugin/Contents/MacOS/ +// * Data: /obs-studio/plugins/.plugin/Contents/Resources +// +// * Legacy User Path: + +// * Binary: /.plugin/Contents/MacOS/ +// * Data: /.plugin/Contents/Resources +// +// * Legacy System Root Path: /Library/Application Support +// * Legacy binary: /obs-studio/plugins//bin/.so +// * Legacy data: /obs-studio/plugins//data +// +// * Legacy User Root Path: /Library/Application Support +// * Legacy binary: /obs-studio/plugins//bin/.so +// * Legacy data: /obs-studio/plugins//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 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 = 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, 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, 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, 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, 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 diff --git a/frontend/plugin-manager/PluginModuleLoader_Windows.cpp b/frontend/plugin-manager/PluginModuleLoader_Windows.cpp new file mode 100644 index 00000000000000..e78f15527053df --- /dev/null +++ b/frontend/plugin-manager/PluginModuleLoader_Windows.cpp @@ -0,0 +1,193 @@ +/****************************************************************************** + Copyright (C) 2026 by FiniteSingularity + + 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 . +******************************************************************************/ + +#include "PluginModuleLoader.hpp" +#include "PluginManager.hpp" + +#include +#include +#include + +#include +#include +#include + +#define WIN32_LEAN_AND_MEAN +#include + +// Windows 3rd-Party Plugin Locations +// +// * User Path: +// * Binary: //.dll +// * Data: //data +// +// * Root Path: C:/ProgramData +// * Binary: /obs-studio/plugins//.dll +// * Data: /obs-studio/plugins//data +// +// * Portable Root Path: +// * Binary: /../../plugins//.dll +// * Data: /../../plugins//data +// +// * Legacy User Path: + +// * Binary: /.dll +// * Data: //data +// +// * Legacy Root Path: +// * Binary: /../../obs-plugins/.dll +// * Data: /../../data/obs-plugins/ +// +// * Legacy Portable Path: +// * Binary: /../../obs-plugins/.dll +// * Data: /../../data/obs-plugins/ +// +// * Legacy System Path: C:/ProgramData +// * Binary: /obs-studio/plugins//bin/.dll +// * Data: /obs-studio/plugins//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/"}; +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 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, 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, 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 = 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 = 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, MODULE_TYPE_LEGACY_PLUGIN, failedModules_); + } + } else { + libraryPluginState = State::Success; + } + + return (userPluginState && portablePluginState && libraryPluginState) ? State::Success : State::PartialFailure; +} +} // namespace OBS diff --git a/frontend/widgets/OBSBasic.cpp b/frontend/widgets/OBSBasic.cpp index e8ddd25fc1c739..ce02584cbebf7b 100644 --- a/frontend/widgets/OBSBasic.cpp +++ b/frontend/widgets/OBSBasic.cpp @@ -112,103 +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 unsafe_modules = { @@ -216,42 +119,6 @@ static const unordered_set unsafe_modules = { "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) @@ -1060,27 +927,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 failed_modules = mfi.failed_modules; + App()->loadAppModules(); #ifdef BROWSER_AVAILABLE cef = obs_browser_init_panel(); @@ -1399,22 +1254,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() diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h index 8898243eed22da..924ff04bd4dbe7 100644 --- a/libobs/obs-internal.h +++ b/libobs/obs-internal.h @@ -578,6 +578,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; diff --git a/libobs/obs-module.c b/libobs/obs-module.c index 9e415e41cecfeb..b46ad4060143c8 100644 --- a/libobs/obs-module.c +++ b/libobs/obs-module.c @@ -557,53 +557,47 @@ static void load_all_callback(void *param, const struct obs_module_info2 *info) } } -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); + return; } -static const char *obs_load_all_modules2_name = "obs_load_all_modules2"; - 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); + return; +} - profile_start(obs_load_all_modules2_name); +bool obs_load_core_modules() +{ + if (obs->core_modules_loaded) { + return true; + } - struct fail_info core_fail_info = {0}; - load_core_modules(load_all_callback, &core_fail_info); + struct fail_info error = {0}; - if (core_fail_info.fail_count > 0) { - fail_info.fail_count += core_fail_info.fail_count; + load_core_modules(load_all_callback, &error); - dstr_insert_dstr(&fail_info.fail_modules, 0, &core_fail_info.fail_modules); - } - dstr_free(&core_fail_info.fail_modules); + bool has_core_module_failure = error.fail_count > 0; - 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); + dstr_free(&error.fail_modules); - mfi->count = fail_info.fail_count; - mfi->failed_modules = strlist_split(fail_info.fail_modules.array, ';', false); - dstr_free(&fail_info.fail_modules); + return !has_core_module_failure; +} + +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}; + + 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) @@ -614,8 +608,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(); @@ -690,6 +693,15 @@ bool obs_is_core_module(obs_module_t *module) return module->module_type == MODULE_TYPE_CORE; } +bool obs_is_legacy_module(obs_module_t *module) +{ + if (!module) { + return false; + } + + return module->module_type == 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}; diff --git a/libobs/obs.c b/libobs/obs.c index c23fd33277d92d..fc8a0967b121c8 100644 --- a/libobs/obs.c +++ b/libobs/obs.c @@ -1260,6 +1260,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; } diff --git a/libobs/obs.h b/libobs/obs.h index 6a4b76ce8527da..5d59e22d7d83c5 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -588,7 +588,7 @@ EXPORT void obs_add_safe_module(const char *name); 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; @@ -596,7 +596,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. */ @@ -626,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(); +/** 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); From 9afd3149238f26f44dc511c0e0f55dc1a36e9f24 Mon Sep 17 00:00:00 2001 From: FiniteSingularity Date: Thu, 30 Jul 2026 11:46:06 -0500 Subject: [PATCH 6/8] Adds separate Flatpak plugin loading sorce files. --- CMakePresets.json | 3 +- build-aux/com.obsproject.Studio.json | 1 + cmake/common/osconfig.cmake | 9 ++ frontend/cmake/feature-plugin-manager.cmake | 3 + .../plugin-manager/LoaderPaths_Flatpak.hpp.in | 13 ++ .../plugin-manager/PluginModuleLoader.hpp | 2 + .../PluginModuleLoader_Flatpak.cpp | 152 ++++++++++++++++++ .../PluginModuleLoader_Linux.cpp | 29 +--- 8 files changed, 183 insertions(+), 29 deletions(-) create mode 100644 frontend/plugin-manager/LoaderPaths_Flatpak.hpp.in create mode 100644 frontend/plugin-manager/PluginModuleLoader_Flatpak.cpp diff --git a/CMakePresets.json b/CMakePresets.json index b43ca9d131c3ac..bfba2c95b2a13c 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -139,7 +139,8 @@ "ENABLE_AJA": false, "ENABLE_VLC": true, "ENABLE_WAYLAND": true, - "ENABLE_WEBRTC": false + "ENABLE_WEBRTC": false, + "ENABLE_FLATPAK": false } }, { diff --git a/build-aux/com.obsproject.Studio.json b/build-aux/com.obsproject.Studio.json index 074957c906086d..6e65e5936addd4 100644 --- a/build-aux/com.obsproject.Studio.json +++ b/build-aux/com.obsproject.Studio.json @@ -131,6 +131,7 @@ "builddir": true, "config-opts": [ "-DCMAKE_BUILD_TYPE=RelWithDebInfo", + "-DENABLE_FLATPAK=ON", "-DENABLE_WAYLAND=ON", "-DENABLE_BROWSER=ON", "-DCEF_ROOT_DIR=/app/cef", diff --git a/cmake/common/osconfig.cmake b/cmake/common/osconfig.cmake index 2dc6dedb889614..4c301470e1c72a 100644 --- a/cmake/common/osconfig.cmake +++ b/cmake/common/osconfig.cmake @@ -17,4 +17,13 @@ elseif(CMAKE_HOST_SYSTEM_NAME MATCHES "Linux|FreeBSD|OpenBSD") list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/linux") string(TOUPPER "${CMAKE_HOST_SYSTEM_NAME}" _SYSTEM_NAME_U) set(OS_${_SYSTEM_NAME_U} TRUE) + + option(ENABLE_FLATPAK "Enable Flatpak-specific implementations." OFF) + mark_as_advanced(ENABLE_FLATPAK) + + if(OS_LINUX AND ENABLE_FLATPAK) + set(OS_FLATPAK TRUE) + else() + set(OS_FLATPAK FALSE) + endif() endif() diff --git a/frontend/cmake/feature-plugin-manager.cmake b/frontend/cmake/feature-plugin-manager.cmake index 949c3b02082f6d..5be57d9ac29853 100644 --- a/frontend/cmake/feature-plugin-manager.cmake +++ b/frontend/cmake/feature-plugin-manager.cmake @@ -22,6 +22,9 @@ 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) diff --git a/frontend/plugin-manager/LoaderPaths_Flatpak.hpp.in b/frontend/plugin-manager/LoaderPaths_Flatpak.hpp.in new file mode 100644 index 00000000000000..018409af15bdbb --- /dev/null +++ b/frontend/plugin-manager/LoaderPaths_Flatpak.hpp.in @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace OBS { + namespace 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@"}; + } +} diff --git a/frontend/plugin-manager/PluginModuleLoader.hpp b/frontend/plugin-manager/PluginModuleLoader.hpp index 3865912940e3c9..0ea0de5b75f584 100644 --- a/frontend/plugin-manager/PluginModuleLoader.hpp +++ b/frontend/plugin-manager/PluginModuleLoader.hpp @@ -31,6 +31,8 @@ using ModuleList = std::vector; 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 constexpr std::string_view kXDGConfigHomeVariable{"XDG_CONFIG_HOME"}; +inline constexpr std::string_view kXDGDataHomeVariable{"XDG_DATA_HOME"}; inline std::string getEnvironmentVariable(std::string_view variableName) { diff --git a/frontend/plugin-manager/PluginModuleLoader_Flatpak.cpp b/frontend/plugin-manager/PluginModuleLoader_Flatpak.cpp new file mode 100644 index 00000000000000..de6db254490c2f --- /dev/null +++ b/frontend/plugin-manager/PluginModuleLoader_Flatpak.cpp @@ -0,0 +1,152 @@ +/****************************************************************************** + Copyright (C) 2026 by FiniteSingularity + + 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 . +******************************************************************************/ + +#include "PluginModuleLoader.hpp" +#include "PluginManager.hpp" +#include + +#include +#include +#include + +#include +#include +#include +#include + +// Flatpak 3rd-Party Plugin Locations +// +// * Flatppak Path: /app/plugins +// * Binary: /app/plugins//obs-modules/plugins/.so +// * Data: /app/plugins//share/obs/obs-modules/plugins/ +// +// * XDG Data Home Path: /home//.local/share +// * Binary: /obs-studio/plugins//.so +// * Data: /obs-studio/plugins//data +// +// * XDG Config Home Path: /home//.config +// * Binary: /obs-studio/plugins//bin/64bit/.so +// * Data: /obs-studio/plugins//data +// +// * Legacy Flatpak Root Path: /app/plugins +// * Binary: //obs-plugins/.so +// * Data: /share/obs/obs-plugins/ +// + +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 kFlatpakPath{"/app/plugins/"}; +constexpr std::string_view kLegacyFlatpakPath{"/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) +{ + UNUSED_PARAMETER(usePortableMode); + blog(LOG_INFO, "Loading Flatpak plugins..."); + State flatpakPluginState = State::Failure; + + { + std::string binaryPath{kFlatpakPath}; + std::string dataPath{kFlatpakPath}; + binaryPath.append(Constants::kPlatformLibraryPath); + binaryPath.append("obs-modules/plugins/"); + dataPath.append(Constants::kPlatformDataPath); + dataPath.append("obs-modules/plugins/%module%"); + blog(LOG_INFO, " Loading Flatpak plugins from binary path: %s and data path: %s", binaryPath.c_str(), dataPath.c_str()); + ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()}, + .type = MODULE_TYPE_PLUGIN, + .name = nullptr}; + + flatpakPluginState = pluginLoadHelper(info, failedModules_); + } + + State xdgDataHomeState = State::Failure; + std::string xdgDataHomePath = getEnvironmentVariable(kXDGDataHomeVariable); + if (!xdgDataHomePath.empty()) { + std::string binaryPath{xdgDataHomePath}; + std::string dataPath{xdgDataHomePath}; + binaryPath.append(kConfigBinaryPath); + dataPath.append(kConfigDataPath); + blog(LOG_INFO, " Loading XDG data home plugins from binary path: %s and data path: %s", binaryPath.c_str(), dataPath.c_str()); + ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()}, + .type = 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) +{ + UNUSED_PARAMETER(usePortableMode); + blog(LOG_INFO, "Loading Flatpak legacy plugins..."); + State flatpakPluginState = State::Failure; + + { + std::string binaryPath{kLegacyFlatpakPath}; + std::string dataPath{kLegacyFlatpakPath}; + binaryPath.append(Constants::kPlatformLibraryPath); + binaryPath.append("obs-plugins/"); + dataPath.append(Constants::kPlatformDataPath); + dataPath.append("obs/obs-plugins/%module%"); + blog(LOG_INFO, " Loading Flatpak legacy plugins from binary path: %s and data path: %s", binaryPath.c_str(), dataPath.c_str()); + ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()}, + .type = MODULE_TYPE_LEGACY_PLUGIN, + .name = nullptr}; + + flatpakPluginState = pluginLoadHelper(info, failedModules_); + } + + State xdgDataConfigState = State::Failure; + std::string xdgConfigHomePath = getEnvironmentVariable(kXDGConfigHomeVariable); + if (!xdgConfigHomePath.empty()) { + std::string binaryPath{xdgConfigHomePath}; + std::string dataPath{xdgConfigHomePath}; + binaryPath.append(kLegacyConfigBinaryPath); + dataPath.append(kLegacyConfigDataPath); + blog(LOG_INFO, " Loading XDG config legacy plugins from binary path: %s and data path: %s", binaryPath.c_str(), dataPath.c_str()); + ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()}, + .type = MODULE_TYPE_LEGACY_PLUGIN, + .name = nullptr}; + xdgDataConfigState = pluginLoadHelper(info, failedModules_); + } + + return (flatpakPluginState && xdgDataConfigState) ? State::Success : State::PartialFailure; +} +} // namespace OBS diff --git a/frontend/plugin-manager/PluginModuleLoader_Linux.cpp b/frontend/plugin-manager/PluginModuleLoader_Linux.cpp index 9f214ffdca4a9b..248923ebd24c71 100644 --- a/frontend/plugin-manager/PluginModuleLoader_Linux.cpp +++ b/frontend/plugin-manager/PluginModuleLoader_Linux.cpp @@ -57,11 +57,6 @@ // * Legacy Fallback Path: // * Binary: /../../obs-plugins/64bit/.so // * Data: /share/obs/obs-plugins/ -// -// * Legacy Flatpak Root Path: /app/plugins -// * Binary: //obs-plugins/.so -// * Data: /share/obs/obs-plugins/ -// using State = OBS::PluginManager::State; using ModuleType = obs_runtime_module_type; @@ -77,7 +72,6 @@ constexpr std::string_view kUserBinaryPath{"/%module%"}; constexpr std::string_view kUserDataPath{"/%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 kLegacyFlatpakPath{"/app/plugins/"}; namespace { State pluginLoadHelper(const ModuleLoadInfo &info, ModuleList &failedModules) @@ -219,28 +213,7 @@ State PluginManager::loadLegacyPlugins(bool usePortableMode) } } - State flatpakPluginState = State::Failure; - - { - std::string binaryPath{kLegacyFlatpakPath}; - std::string dataPath{kLegacyFlatpakPath}; - 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 = MODULE_TYPE_LEGACY_PLUGIN, - .name = nullptr}; - - flatpakPluginState = pluginLoadHelper(info, failedModules_); - } - - return (userPluginState && systemPluginState && configPluginState && flatpakPluginState) + return (userPluginState && systemPluginState && configPluginState) ? State::Success : State::PartialFailure; } From 6419f58b081a8accea7d1c0cd749a582e45759e2 Mon Sep 17 00:00:00 2001 From: FiniteSingularity Date: Wed, 5 Aug 2026 09:37:55 -0500 Subject: [PATCH 7/8] Working flatpak, adds module type to module info. --- frontend/plugin-manager/PluginManager.cpp | 2 +- .../plugin-manager/PluginModuleLoader_Flatpak.cpp | 13 +++++-------- libobs/obs-internal.h | 1 + libobs/obs-module.c | 15 ++++++++++++--- libobs/obs.h | 1 + 5 files changed, 20 insertions(+), 12 deletions(-) diff --git a/frontend/plugin-manager/PluginManager.cpp b/frontend/plugin-manager/PluginManager.cpp index 8e6a51b57e2103..6000102efa5d2c 100644 --- a/frontend/plugin-manager/PluginManager.cpp +++ b/frontend/plugin-manager/PluginManager.cpp @@ -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_is_core_module(newModule)) { + if (obs_is_core_module(newModule)) { return; } diff --git a/frontend/plugin-manager/PluginModuleLoader_Flatpak.cpp b/frontend/plugin-manager/PluginModuleLoader_Flatpak.cpp index de6db254490c2f..a689fcaeae37d4 100644 --- a/frontend/plugin-manager/PluginModuleLoader_Flatpak.cpp +++ b/frontend/plugin-manager/PluginModuleLoader_Flatpak.cpp @@ -17,7 +17,7 @@ #include "PluginModuleLoader.hpp" #include "PluginManager.hpp" -#include +#include #include #include @@ -34,6 +34,10 @@ // * Binary: /app/plugins//obs-modules/plugins/.so // * Data: /app/plugins//share/obs/obs-modules/plugins/ // +// * Legacy Flatpak Root Path: /app/plugins +// * Binary: //obs-plugins/.so +// * Data: /share/obs/obs-plugins/ +// // * XDG Data Home Path: /home//.local/share // * Binary: /obs-studio/plugins//.so // * Data: /obs-studio/plugins//data @@ -41,11 +45,6 @@ // * XDG Config Home Path: /home//.config // * Binary: /obs-studio/plugins//bin/64bit/.so // * Data: /obs-studio/plugins//data -// -// * Legacy Flatpak Root Path: /app/plugins -// * Binary: //obs-plugins/.so -// * Data: /share/obs/obs-plugins/ -// using State = OBS::PluginManager::State; using ModuleType = obs_runtime_module_type; @@ -74,7 +73,6 @@ namespace OBS { State PluginManager::loadPlugins(bool usePortableMode) { UNUSED_PARAMETER(usePortableMode); - blog(LOG_INFO, "Loading Flatpak plugins..."); State flatpakPluginState = State::Failure; { @@ -115,7 +113,6 @@ State PluginManager::loadPlugins(bool usePortableMode) State PluginManager::loadLegacyPlugins(bool usePortableMode) { UNUSED_PARAMETER(usePortableMode); - blog(LOG_INFO, "Loading Flatpak legacy plugins..."); State flatpakPluginState = State::Failure; { diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h index 924ff04bd4dbe7..99a35e34eee829 100644 --- a/libobs/obs-internal.h +++ b/libobs/obs-internal.h @@ -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) diff --git a/libobs/obs-module.c b/libobs/obs-module.c index b46ad4060143c8..5523e823fad5e4 100644 --- a/libobs/obs-module.c +++ b/libobs/obs-module.c @@ -544,6 +544,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); @@ -587,9 +590,14 @@ bool obs_load_core_modules() 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}; + 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}; @@ -674,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; @@ -775,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); } @@ -791,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); diff --git a/libobs/obs.h b/libobs/obs.h index 5d59e22d7d83c5..5adebd3f403b03 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -616,6 +616,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); From 4e0b9cd06fe8bcac0414ec6e7457829f60bb2bad Mon Sep 17 00:00:00 2001 From: FiniteSingularity Date: Wed, 5 Aug 2026 09:47:38 -0500 Subject: [PATCH 8/8] Flatpak json file. --- build-aux/com.obsproject.Studio.json | 33 ++++++---------------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/build-aux/com.obsproject.Studio.json b/build-aux/com.obsproject.Studio.json index 6e65e5936addd4..e52d3c78cdd2c6 100644 --- a/build-aux/com.obsproject.Studio.json +++ b/build-aux/com.obsproject.Studio.json @@ -3,6 +3,11 @@ "runtime": "org.freedesktop.Platform", "runtime-version": "25.08", "sdk": "org.freedesktop.Sdk", + "branch": "test", + "separate-locales": false, + "build-options": { + "no-debuginfo": true + }, "command": "obs", "finish-args": [ "--socket=wayland", @@ -24,7 +29,7 @@ "directory": "plugins", "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 }, @@ -101,30 +106,6 @@ } ] }, - { - "name": "cef", - "build-options": { - "no-debuginfo": true - }, - "buildsystem": "simple", - "build-commands": [ - "mkdir -p /app/cef/libcef_dll_wrapper", - "cp -R ./include /app/cef", - "cp -R ./Release /app/cef", - "cp -R ./Resources /app/cef", - "cp -R ./build/libcef_dll_wrapper/libcef_dll_wrapper.a /app/cef/libcef_dll_wrapper" - ], - "cleanup": [ - "*" - ], - "sources": [ - { - "type": "archive", - "url": "https://cdn-fastly.obsproject.com/downloads/cef_binary_6533_linux_x86_64_v6.tar.xz", - "sha256": "7963335519a19ccdc5233f7334c5ab023026e2f3e9a0cc417007c09d86608146" - } - ] - }, { "name": "obs", "buildsystem": "cmake-ninja", @@ -133,7 +114,7 @@ "-DCMAKE_BUILD_TYPE=RelWithDebInfo", "-DENABLE_FLATPAK=ON", "-DENABLE_WAYLAND=ON", - "-DENABLE_BROWSER=ON", + "-DENABLE_BROWSER=OFF", "-DCEF_ROOT_DIR=/app/cef", "-DENABLE_ALSA=OFF", "-DENABLE_PULSEAUDIO=ON",