Skip to main content

libobs_wrapper\sources/
mod.rs

1//! This module holds everything related to sources.
2//! A source renders specific content to the scene, which is then processed by the ObsOutputRef and for example
3//! written to a file using encoders.
4mod builder;
5pub use builder::*;
6
7mod traits;
8pub use traits::*;
9
10mod macros;
11
12mod filter;
13pub use filter::*;
14
15use libobs::obs_source_t;
16
17use crate::{
18    data::{
19        object::{inner_fn_update_settings, ObsObjectTrait, ObsObjectTraitPrivate},
20        ImmutableObsData, ObsDataPointers,
21    },
22    impl_obs_drop, impl_signal_manager, run_with_obs,
23    runtime::ObsRuntime,
24    unsafe_send::{Sendable, SmartPointerSendable},
25    utils::{ObsDropGuard, ObsError, ObsString, SourceInfo},
26};
27
28use std::sync::{Arc, RwLock};
29
30#[derive(Debug, Clone)]
31#[allow(dead_code)]
32pub struct ObsSourceRef {
33    /// Disconnect signals first
34    signal_manager: Arc<ObsSourceSignals>,
35
36    id: ObsString,
37    name: ObsString,
38    settings: Arc<RwLock<ImmutableObsData>>,
39    hotkey_data: Arc<RwLock<ImmutableObsData>>,
40
41    attached_filters: Arc<RwLock<Vec<ObsFilterGuardPair>>>,
42
43    runtime: ObsRuntime,
44    source: SmartPointerSendable<*mut obs_source_t>,
45}
46
47impl ObsSourceRef {
48    pub fn new_from_info(info: SourceInfo, runtime: ObsRuntime) -> Result<Self, ObsError> {
49        let SourceInfo {
50            id,
51            name,
52            settings,
53            hotkey_data,
54        } = info;
55
56        Self::new(id, name, settings, hotkey_data, runtime)
57    }
58
59    pub fn new<T: Into<ObsString> + Sync + Send, K: Into<ObsString> + Sync + Send>(
60        id: T,
61        name: K,
62        settings: Option<ImmutableObsData>,
63        hotkey_data: Option<ImmutableObsData>,
64        runtime: ObsRuntime,
65    ) -> Result<Self, ObsError> {
66        let id = id.into();
67        let name = name.into();
68
69        // We are creating empty immutable settings here because OBS would do it nonetheless if we passed a null pointer.
70        let hotkey_data = match hotkey_data {
71            Some(x) => x,
72            None => ImmutableObsData::new(&runtime)?,
73        };
74
75        let hotkey_data_ptr = hotkey_data.as_ptr();
76        let settings_ptr = settings.map(|x| x.as_ptr());
77
78        let source_ptr = run_with_obs!(
79            runtime,
80            (hotkey_data_ptr, settings_ptr, id, name),
81            move || {
82                let id_ptr = id.as_ptr().0;
83                let name_ptr = name.as_ptr().0;
84
85                let settings_raw_ptr = match settings_ptr {
86                    Some(s) => s.get_ptr(),
87                    None => std::ptr::null_mut(),
88                };
89
90                let source_ptr = unsafe {
91                    // Safety: Id, Name must be valid pointers because they are not dropped. Also the settings_ptr and hotkey_data_ptr may be null, its fine.
92                    libobs::obs_source_create(
93                        id_ptr,
94                        name_ptr,
95                        settings_raw_ptr,
96                        hotkey_data_ptr.get_ptr(),
97                    )
98                };
99
100                if source_ptr.is_null() {
101                    Err(ObsError::NullPointer(None))
102                } else {
103                    Ok(Sendable(source_ptr))
104                }
105            }
106        )??;
107
108        let source_ptr = SmartPointerSendable::new(
109            source_ptr.0,
110            Arc::new(_ObsSourceGuard {
111                source: source_ptr.clone(),
112                runtime: runtime.clone(),
113            }),
114            runtime.native_registry(),
115        );
116
117        // Getting default settings if none were provided
118        let settings = {
119            let default_settings_ptr = run_with_obs!(runtime, (source_ptr), move || {
120                unsafe {
121                    // Safety: This safe to call because we are using a smart pointer and the source pointer must not be dropped.
122                    Sendable(libobs::obs_source_get_settings(source_ptr.get_ptr()))
123                }
124            })?;
125
126            ImmutableObsData::from_raw_pointer(default_settings_ptr, runtime.clone())
127        };
128
129        let signals = ObsSourceSignals::new(&source_ptr, runtime.clone())?;
130        Ok(Self {
131            source: source_ptr.clone(),
132            id,
133            name,
134            settings: Arc::new(RwLock::new(settings)),
135            hotkey_data: Arc::new(RwLock::new(hotkey_data)),
136            attached_filters: Arc::new(RwLock::new(Vec::new())),
137            runtime,
138            signal_manager: Arc::new(signals),
139        })
140    }
141}
142
143impl ObsObjectTraitPrivate for ObsSourceRef {
144    fn __internal_replace_settings(&self, settings: ImmutableObsData) -> Result<(), ObsError> {
145        let mut guard = self
146            .settings
147            .write()
148            .map_err(|_| ObsError::LockError("Failed to acquire write lock on settings".into()))?;
149
150        *guard = settings;
151        Ok(())
152    }
153
154    fn __internal_replace_hotkey_data(
155        &self,
156        hotkey_data: ImmutableObsData,
157    ) -> Result<(), ObsError> {
158        let mut guard = self.hotkey_data.write().map_err(|_| {
159            ObsError::LockError("Failed to acquire write lock on hotkey data".into())
160        })?;
161
162        *guard = hotkey_data;
163        Ok(())
164    }
165}
166
167impl ObsObjectTrait for ObsSourceRef {
168    type Native = *mut libobs::obs_source_t;
169
170    fn __native_handle(&self) -> SmartPointerSendable<Self::Native> {
171        self.source.clone()
172    }
173
174    fn runtime(&self) -> &ObsRuntime {
175        &self.runtime
176    }
177
178    fn settings(&self) -> Result<ImmutableObsData, ObsError> {
179        let res = self
180            .settings
181            .read()
182            .map_err(|_| ObsError::LockError("Failed to acquire read lock on settings".into()))?
183            .clone();
184
185        Ok(res)
186    }
187
188    fn hotkey_data(&self) -> Result<ImmutableObsData, ObsError> {
189        let res = self
190            .hotkey_data
191            .read()
192            .map_err(|_| ObsError::LockError("Failed to acquire read lock on hotkey data".into()))?
193            .clone();
194
195        Ok(res)
196    }
197
198    fn id(&self) -> ObsString {
199        self.id.clone()
200    }
201
202    fn name(&self) -> ObsString {
203        self.name.clone()
204    }
205
206    fn update_settings(&self, settings: crate::data::ObsData) -> Result<(), ObsError> {
207        inner_fn_update_settings!(self, libobs::obs_source_update, settings)
208    }
209}
210
211impl ObsSourceTrait for ObsSourceRef {
212    fn signals(&self) -> &Arc<ObsSourceSignals> {
213        &self.signal_manager
214    }
215
216    fn get_active_filters(&self) -> Result<Vec<ObsFilterGuardPair>, ObsError> {
217        let guard = self.attached_filters.read().map_err(|_| {
218            ObsError::LockError("Failed to acquire read lock on attached filters".into())
219        })?;
220
221        Ok(guard.clone())
222    }
223
224    fn apply_filter(&self, filter: &ObsFilterRef) -> Result<(), ObsError> {
225        self.runtime.ensure_same_runtime(filter.runtime())?;
226        let mut guard = self.attached_filters.write().map_err(|_| {
227            ObsError::LockError("Failed to acquire write lock on attached filters".into())
228        })?;
229
230        let source_ptr = self.__native_handle();
231        let filter_ptr = filter.__native_handle();
232
233        let has_filter = guard.iter().any(|f| {
234            f.get_inner().__native_handle().native_id() == filter.__native_handle().native_id()
235        });
236
237        if has_filter {
238            return Err(ObsError::FilterAlreadyApplied);
239        }
240
241        run_with_obs!(self.runtime(), (source_ptr, filter_ptr), move || unsafe {
242            // Safety: Both pointers are valid because of the smart pointers.
243            libobs::obs_source_filter_add(source_ptr.get_ptr(), filter_ptr.get_ptr());
244            Ok(())
245        })??;
246
247        let runtime = self.runtime().clone();
248        let drop_guard = _ObsRemoveFilterOnDrop::new(
249            self.__native_handle(),
250            filter.__native_handle(),
251            None,
252            runtime,
253        );
254
255        guard.push(ObsFilterGuardPair::new(
256            filter.clone(),
257            Arc::new(drop_guard),
258        ));
259
260        Ok(())
261    }
262}
263
264impl_signal_manager!(|ptr: SmartPointerSendable<*mut libobs::obs_source_t>| unsafe {
265    // Safety: We are using a smart pointer, so it is fine
266    libobs::obs_source_get_signal_handler(ptr.get_ptr())
267}, ObsSourceSignals for *mut libobs::obs_source_t, [
268    "destroy": {},
269    "remove": {},
270    "update": {},
271    "save": {},
272    "load": {},
273    "activate": {},
274    "deactivate": {},
275    "show": {},
276    "hide": {},
277    "mute": { struct MuteSignal {
278        muted: bool
279    } },
280    "push_to_mute_changed": {struct PushToMuteChangedSignal {
281        enabled: bool
282    }},
283    "push_to_mute_delay": {struct PushToMuteDelaySignal {
284        delay: i64
285    }},
286    "push_to_talk_changed": {struct PushToTalkChangedSignal {
287        enabled: bool
288    }},
289    "push_to_talk_delay": {struct PushToTalkDelaySignal {
290        delay: i64
291    }},
292    "enable": {struct EnableSignal {
293        enabled: bool
294    }},
295    "rename": {struct NewNameSignal {
296        new_name: String,
297        prev_name: String
298    }},
299    "update_properties": {},
300    "update_flags": {struct UpdateFlagsSignal {
301        flags: i64
302    }},
303    "audio_sync": {struct AudioSyncSignal {
304        offset: i64,
305    }},
306    "audio_balance": {struct AudioBalanceSignal {
307        balance: f64,
308    }},
309    "audio_mixers": {struct AudioMixersSignal {
310        mixers: i64,
311    }},
312    "audio_activate": {},
313    "audio_deactivate": {},
314    "filter_add": {struct FilterAddSignal {
315        POINTERS {
316            filter: *mut libobs::obs_source_t,
317        }
318    }},
319    "filter_remove": {struct FilterRemoveSignal {
320        POINTERS {
321            filter: *mut libobs::obs_source_t,
322        }
323    }},
324    "reorder_filters": {},
325    "transition_start": {},
326    "transition_video_stop": {},
327    "transition_stop": {},
328    "media_started": {},
329    "media_ended":{},
330    "media_pause": {},
331    "media_play": {},
332    "media_restart": {},
333    "media_stopped": {},
334    "media_next": {},
335    "media_previous": {},
336]);
337
338#[derive(Debug)]
339struct _ObsSourceGuard {
340    source: Sendable<*mut obs_source_t>,
341    runtime: ObsRuntime,
342}
343
344impl ObsDropGuard for _ObsSourceGuard {}
345
346impl_obs_drop!(_ObsSourceGuard, (source), move || unsafe {
347    // Safety: We are in the runtime and the pointer is valid because of the drop guard
348    libobs::obs_source_release(source.0);
349});