libobs_wrapper\utils/
modules.rs1use std::{
2 ffi::{CStr, CString},
3 fmt::Debug,
4};
5
6use crate::{
7 context::ObsContext, enums::ObsLogLevel, logger::internal_log_global, run_with_obs,
8 runtime::ObsRuntime, unsafe_send::Sendable, utils::StartupPaths,
9};
10use libobs::obs_module_failure_info;
11
12pub struct ObsModules {
13 paths: StartupPaths,
14
15 info: Option<Sendable<obs_module_failure_info>>,
17 pub(crate) runtime: Option<ObsRuntime>,
18}
19
20impl Debug for ObsModules {
21 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22 f.debug_struct("ObsModules")
23 .field("paths", &self.paths)
24 .field("info", &"(internal obs_module_failure_info)")
25 .finish()
26 }
27}
28
29static SAFE_MODULES: &str = "decklink|image-source|linux-alsa|linux-capture|linux-pipewire|linux-pulseaudio|linux-v4l2|obs-ffmpeg|obs-filters|obs-nvenc|obs-outputs|obs-qsv11|obs-transitions|obs-vst|obs-websocket|obs-x264|rtmp-services|text-freetype2|vlc-video|decklink-captions|decklink-output-ui|obslua|obspython|frontend-tools";
31
32impl ObsModules {
33 #[allow(unknown_lints)]
35 #[allow(ensure_obs_call_in_runtime)]
36 pub(crate) unsafe fn add_paths(paths: &StartupPaths) -> Self {
37 internal_log_global(
38 ObsLogLevel::Info,
39 "[libobs-wrapper]: Adding module paths:".to_string(),
40 );
41 internal_log_global(
42 ObsLogLevel::Info,
43 format!(
44 "[libobs-wrapper]: libobs data path: {}",
45 paths.libobs_data_path()
46 ),
47 );
48 internal_log_global(
49 ObsLogLevel::Info,
50 format!(
51 "[libobs-wrapper]: plugin bin path: {}",
52 paths.plugin_bin_path()
53 ),
54 );
55 internal_log_global(
56 ObsLogLevel::Info,
57 format!(
58 "[libobs-wrapper]: plugin data path: {}",
59 paths.plugin_data_path()
60 ),
61 );
62
63 libobs::obs_add_data_path(paths.libobs_data_path().as_ptr().0);
64 libobs::obs_add_module_path(
65 paths.plugin_bin_path().as_ptr().0,
66 paths.plugin_data_path().as_ptr().0,
67 );
68
69 #[allow(unused_mut)]
70 let mut disabled_plugins = vec!["obs-websocket", "frontend-tools"];
71
72 #[cfg(feature = "__test_environment")]
73 {
74 disabled_plugins.extend(&["decklink-output-ui", "decklink-captions", "decklink"]);
75 }
76
77 let version = ObsContext::get_version_global().unwrap_or_default();
78 let version_parts: Vec<&str> = version.split('.').collect();
79 let major = version_parts
80 .first()
81 .and_then(|s| s.parse::<u32>().ok())
82 .unwrap_or(0);
83
84 #[cfg(target_os = "linux")]
86 let has_disabled_module_fn = {
87 let symbol_name = CString::new("obs_add_disabled_module").unwrap();
89 let sym = libc::dlsym(libc::RTLD_DEFAULT, symbol_name.as_ptr());
90 let found = !sym.is_null();
91
92 if !found && major >= 32 {
93 log::warn!("OBS version >= 32 but obs_add_disabled_module symbol not found, falling back to safe modules");
94 }
95
96 found
97 };
98 #[cfg(not(target_os = "linux"))]
99 let has_disabled_module_fn = major >= 32;
100
101 if major >= 32 && has_disabled_module_fn {
102 for plugin in disabled_plugins {
103 let c_str = CString::new(plugin).unwrap();
104 #[cfg(target_os = "linux")]
105 {
106 let symbol_name = CString::new("obs_add_disabled_module").unwrap();
107 let func = libc::dlsym(libc::RTLD_DEFAULT, symbol_name.as_ptr());
108 if !func.is_null() {
109 let add_disabled: extern "C" fn(*const std::os::raw::c_char) =
110 std::mem::transmute(func);
111 add_disabled(c_str.as_ptr());
112 }
113 }
114 #[cfg(not(target_os = "linux"))]
115 {
116 libobs::obs_add_disabled_module(c_str.as_ptr());
117 }
118 }
119 } else {
120 for plugin in SAFE_MODULES.split('|') {
121 if disabled_plugins.contains(&plugin) {
122 continue;
123 }
124 let c_str = CString::new(plugin).unwrap();
125 libobs::obs_add_safe_module(c_str.as_ptr());
126 }
127 }
128
129 Self {
130 paths: paths.clone(),
131 info: None,
132 runtime: None,
133 }
134 }
135
136 #[allow(unknown_lints)]
138 #[allow(ensure_obs_call_in_runtime)]
139 pub(crate) unsafe fn load_modules(&mut self) {
140 let mut failure_info: obs_module_failure_info = std::mem::zeroed();
141 internal_log_global(
142 ObsLogLevel::Info,
143 "---------------------------------".to_string(),
144 );
145 libobs::obs_load_all_modules2(&mut failure_info);
146 internal_log_global(
147 ObsLogLevel::Info,
148 "---------------------------------".to_string(),
149 );
150 libobs::obs_log_loaded_modules();
151 internal_log_global(
152 ObsLogLevel::Info,
153 "---------------------------------".to_string(),
154 );
155 libobs::obs_post_load_modules();
156 self.info = Some(Sendable(failure_info));
157
158 self.log_if_failed();
159 }
160
161 #[cfg_attr(coverage_nightly, coverage(off))]
162 #[allow(unknown_lints)]
163 #[allow(ensure_obs_call_in_runtime)]
164 unsafe fn log_if_failed(&self) {
165 if self.info.as_ref().is_none_or(|x| x.0.count == 0) {
166 return;
167 }
168
169 let info = &self.info.as_ref().unwrap().0;
170 let mut failed_modules = Vec::new();
171 for i in 0..info.count {
172 let module = info.failed_modules.add(i);
173 let plugin_name = CStr::from_ptr(*module);
174 failed_modules.push(plugin_name.to_string_lossy());
175 }
176
177 internal_log_global(
178 ObsLogLevel::Warning,
179 format!("Failed to load modules: {}", failed_modules.join(", ")),
180 );
181 }
182}
183
184impl Drop for ObsModules {
185 fn drop(&mut self) {
186 log::trace!("Dropping ObsModules and removing module paths...");
187
188 let paths = self.paths.clone();
189 let runtime = self.runtime.take().unwrap();
190
191 #[cfg(any(
192 not(feature = "no_blocking_drops"),
193 test,
194 feature = "__test_environment",
195 not(feature = "enable_runtime")
196 ))]
197 {
198 let data_path = paths.libobs_data_path().clone();
199 let r = run_with_obs!(runtime, (data_path), move || unsafe {
200 libobs::obs_remove_data_path(data_path.as_ptr().0);
202 });
203
204 if std::thread::panicking() {
205 return;
206 }
207
208 r.unwrap();
209 }
210
211 #[cfg(all(
212 feature = "no_blocking_drops",
213 not(test),
214 not(feature = "__test_environment"),
215 feature = "enable_runtime"
216 ))]
217 {
218 let _ = tokio::task::spawn_blocking(move || {
219 run_with_obs!(runtime, move || unsafe {
220 libobs::obs_remove_data_path(paths.libobs_data_path().as_ptr().0);
221 })
222 .unwrap();
223 });
224 }
225 }
226}