Skip to main content

libobs_wrapper\utils\info/
startup.rs

1use crate::{
2    context::ObsContext,
3    data::{audio::ObsAudioInfo, video::ObsVideoInfo},
4    logger::{ConsoleLogger, ObsLogger},
5    utils::{initialization::NixDisplay, ObsError, ObsPath, ObsString},
6};
7
8/// Contains information to start a libobs context.
9/// This is passed to the creation of `ObsContext`.
10///
11/// ## Platform Notes
12/// On Linux platforms, if your application uses a GUI
13/// framework (like GTK, Qt, etc.), it is crucial to set
14/// the appropriate `NixDisplay` in the `StartupInfo` **if you want to create a preview window**.
15/// This ensures that libobs can correctly interface with
16/// the display server (X11 or Wayland) used by your application.
17/// If this is not set, libobs will not be able to create a preview window and the application will crash.
18#[derive(Debug)]
19pub struct StartupInfo {
20    pub(crate) startup_paths: StartupPaths,
21    pub(crate) obs_video_info: ObsVideoInfo,
22    pub(crate) obs_audio_info: ObsAudioInfo,
23    pub(crate) module_config_path: Option<ObsString>,
24    // Option because logger is taken when creating
25    pub(crate) logger: Option<Box<dyn ObsLogger + Sync + Send>>,
26    pub(crate) start_glib_loop: bool,
27    pub(crate) nix_display: Option<NixDisplay>,
28}
29
30impl StartupInfo {
31    pub fn new() -> StartupInfo {
32        Self::default()
33    }
34
35    #[cfg_attr(coverage_nightly, coverage(off))]
36    pub fn set_startup_paths(mut self, paths: StartupPaths) -> Self {
37        self.startup_paths = paths;
38        self
39    }
40
41    pub fn set_video_info(mut self, ovi: ObsVideoInfo) -> Self {
42        self.obs_video_info = ovi;
43        self
44    }
45
46    /// Sets the root directory used by OBS modules for persistent configuration.
47    ///
48    /// libobs passes this value to modules through `obs_module_config_path`.
49    /// Callers embedding libobs should set an application-owned writable directory;
50    /// leaving it unset preserves libobs's `NULL` behavior, which can cause modules
51    /// that construct config paths to resolve relative to the process working directory.
52    pub fn set_module_config_path(mut self, path: ObsPath) -> Self {
53        self.module_config_path = Some(path.build());
54        self
55    }
56
57    #[cfg_attr(coverage_nightly, coverage(off))]
58    pub fn get_video_info(&self) -> &ObsVideoInfo {
59        &self.obs_video_info
60    }
61
62    #[cfg_attr(coverage_nightly, coverage(off))]
63    pub fn set_logger(mut self, logger: Box<dyn ObsLogger + Sync + Send>) -> Self {
64        self.logger = Some(logger);
65        self
66    }
67
68    #[cfg_attr(coverage_nightly, coverage(off))]
69    pub fn set_start_glib_loop(mut self, start: bool) -> Self {
70        self.start_glib_loop = start;
71        self
72    }
73
74    /// This sets the Nix display (X11 or Wayland) to use when starting libobs.
75    ///
76    /// This is **important** if your application creates any preview windows using libobs on Linux.
77    /// Otherwise if you don't plan to use preview windows **AND** are not using a GUI framework, you can skip this.
78    ///
79    /// Wayland requires this display to be the same as the one used by the GUI application (if you have one).
80    /// Failing to set this may result in libobs being unable to create preview windows,
81    ///
82    /// X11 however works without setting this display, in fact your window may become unresponsive if a display is set.
83    ///
84    /// # Safety
85    /// Make sure that the display is closed AFTER the whole OBS context has been dropped!
86    pub unsafe fn set_nix_display(mut self, display: NixDisplay) -> Self {
87        self.nix_display = Some(display);
88        self
89    }
90
91    #[cfg_attr(coverage_nightly, coverage(off))]
92    pub fn start(self) -> Result<ObsContext, ObsError> {
93        ObsContext::new(self)
94    }
95}
96
97impl Default for StartupInfo {
98    fn default() -> StartupInfo {
99        Self {
100            startup_paths: StartupPaths::default(),
101            obs_video_info: ObsVideoInfo::default(),
102            obs_audio_info: ObsAudioInfo::default(),
103            module_config_path: None,
104            logger: Some(Box::new(ConsoleLogger::new())),
105            start_glib_loop: true,
106            nix_display: None,
107        }
108    }
109}
110
111/// Contains the necessary paths for starting the
112/// libobs context built from `ObsPath`.
113///
114/// Note that these strings are copied when parsed,
115/// meaning that these can be freed immediately
116/// after all three strings have been used.
117#[derive(Clone, Debug)]
118pub struct StartupPaths {
119    libobs_data_path: ObsString,
120    plugin_bin_path: ObsString,
121    plugin_data_path: ObsString,
122}
123
124impl StartupPaths {
125    #[cfg_attr(coverage_nightly, coverage(off))]
126    pub fn new(
127        libobs_data_path: ObsPath,
128        plugin_bin_path: ObsPath,
129        plugin_data_path: ObsPath,
130    ) -> StartupPaths {
131        Self {
132            libobs_data_path: libobs_data_path.build(),
133            plugin_bin_path: plugin_bin_path.build(),
134            plugin_data_path: plugin_data_path.build(),
135        }
136    }
137
138    pub fn libobs_data_path(&self) -> &ObsString {
139        &(self.libobs_data_path)
140    }
141
142    pub fn plugin_bin_path(&self) -> &ObsString {
143        &(self.plugin_bin_path)
144    }
145
146    pub fn plugin_data_path(&self) -> &ObsString {
147        &(self.plugin_data_path)
148    }
149}
150
151impl Default for StartupPaths {
152    fn default() -> Self {
153        StartupPathsBuilder::new().build()
154    }
155}
156
157#[derive(Clone, Debug)]
158pub struct StartupPathsBuilder {
159    libobs_data_path: ObsPath,
160    plugin_bin_path: ObsPath,
161    plugin_data_path: ObsPath,
162}
163
164impl StartupPathsBuilder {
165    fn new() -> Self {
166        #[cfg(target_os = "windows")]
167        return Self {
168            libobs_data_path: ObsPath::from_relative("data/libobs"),
169            plugin_bin_path: ObsPath::from_relative("obs-plugins/64bit"),
170            plugin_data_path: ObsPath::from_relative("data/obs-plugins/%module%"),
171        };
172
173        #[cfg(target_os = "macos")]
174        return Self {
175            libobs_data_path: ObsPath::from_relative("data/libobs"),
176            plugin_bin_path: ObsPath::from_relative("obs-plugins/%module%.plugin/Contents/MacOS"),
177            plugin_data_path: ObsPath::from_relative("data/obs-plugins/%module%"),
178        };
179
180        #[cfg(target_os = "linux")]
181        {
182            let lib_path = match std::env::consts::ARCH {
183                "x86_64" => "/usr/lib/x86_64-linux-gnu",
184                "aarch64" => "/usr/lib/aarch64-linux-gnu",
185                "arm" => "/usr/lib/arm-linux-gnueabihf",
186                _ => "/usr/lib",
187            };
188
189            Self {
190                libobs_data_path: ObsPath::new("/usr/share/obs/libobs"),
191                plugin_bin_path: ObsPath::new(&(lib_path.to_string() + "/obs-plugins/%module%")),
192                plugin_data_path: ObsPath::new("/usr/share/obs/obs-plugins/%module%"),
193            }
194        }
195
196        #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
197        Self {
198            libobs_data_path: ObsPath::from_relative("data/libobs"),
199            plugin_bin_path: ObsPath::from_relative("obs-plugins/%module%"),
200            plugin_data_path: ObsPath::from_relative("data/obs-plugins/%module%"),
201        }
202    }
203
204    pub fn build(self) -> StartupPaths {
205        StartupPaths {
206            libobs_data_path: self.libobs_data_path.build(),
207            plugin_bin_path: self.plugin_bin_path.build(),
208            plugin_data_path: self.plugin_data_path.build(),
209        }
210    }
211
212    #[cfg_attr(coverage_nightly, coverage(off))]
213    pub fn libobs_data_path(mut self, value: ObsPath) -> Self {
214        self.libobs_data_path = value;
215        self
216    }
217
218    #[cfg_attr(coverage_nightly, coverage(off))]
219    pub fn plugin_bin_path(mut self, value: ObsPath) -> Self {
220        self.plugin_bin_path = value;
221        self
222    }
223
224    #[cfg_attr(coverage_nightly, coverage(off))]
225    pub fn plugin_data_path(mut self, value: ObsPath) -> Self {
226        self.plugin_data_path = value;
227        self
228    }
229}
230
231#[cfg_attr(coverage_nightly, coverage(off))]
232impl Default for StartupPathsBuilder {
233    fn default() -> StartupPathsBuilder {
234        Self::new()
235    }
236}
237
238#[cfg(test)]
239mod tests {
240    use super::*;
241
242    #[test]
243    fn module_config_path_is_explicitly_configurable() {
244        let info =
245            StartupInfo::new().set_module_config_path(ObsPath::new("/tmp/clipture-obs-config"));
246        let configured = info
247            .module_config_path
248            .as_ref()
249            .expect("module config path should be retained");
250        assert_eq!(configured.to_string(), "/tmp/clipture-obs-config");
251    }
252}