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        );
115
116        // Getting default settings if none were provided
117        let settings = {
118            let default_settings_ptr = run_with_obs!(runtime, (source_ptr), move || {
119                unsafe {
120                    // Safety: This safe to call because we are using a smart pointer and the source pointer must not be dropped.
121                    Sendable(libobs::obs_source_get_settings(source_ptr.get_ptr()))
122                }
123            })?;
124
125            ImmutableObsData::from_raw_pointer(default_settings_ptr, runtime.clone())
126        };
127
128        let signals = ObsSourceSignals::new(&source_ptr, runtime.clone())?;
129        Ok(Self {
130            source: source_ptr.clone(),
131            id,
132            name,
133            settings: Arc::new(RwLock::new(settings)),
134            hotkey_data: Arc::new(RwLock::new(hotkey_data)),
135            attached_filters: Arc::new(RwLock::new(Vec::new())),
136            runtime,
137            signal_manager: Arc::new(signals),
138        })
139    }
140}
141
142impl ObsObjectTraitPrivate for ObsSourceRef {
143    fn __internal_replace_settings(&self, settings: ImmutableObsData) -> Result<(), ObsError> {
144        let mut guard = self
145            .settings
146            .write()
147            .map_err(|_| ObsError::LockError("Failed to acquire write lock on settings".into()))?;
148
149        *guard = settings;
150        Ok(())
151    }
152
153    fn __internal_replace_hotkey_data(
154        &self,
155        hotkey_data: ImmutableObsData,
156    ) -> Result<(), ObsError> {
157        let mut guard = self.hotkey_data.write().map_err(|_| {
158            ObsError::LockError("Failed to acquire write lock on hotkey data".into())
159        })?;
160
161        *guard = hotkey_data;
162        Ok(())
163    }
164}
165
166impl ObsObjectTrait<*mut libobs::obs_source_t> for ObsSourceRef {
167    fn runtime(&self) -> &ObsRuntime {
168        &self.runtime
169    }
170
171    fn settings(&self) -> Result<ImmutableObsData, ObsError> {
172        let res = self
173            .settings
174            .read()
175            .map_err(|_| ObsError::LockError("Failed to acquire read lock on settings".into()))?
176            .clone();
177
178        Ok(res)
179    }
180
181    fn hotkey_data(&self) -> Result<ImmutableObsData, ObsError> {
182        let res = self
183            .hotkey_data
184            .read()
185            .map_err(|_| ObsError::LockError("Failed to acquire read lock on hotkey data".into()))?
186            .clone();
187
188        Ok(res)
189    }
190
191    fn id(&self) -> ObsString {
192        self.id.clone()
193    }
194
195    fn name(&self) -> ObsString {
196        self.name.clone()
197    }
198
199    fn update_settings(&self, settings: crate::data::ObsData) -> Result<(), ObsError> {
200        inner_fn_update_settings!(self, libobs::obs_source_update, settings)
201    }
202
203    fn as_ptr(&self) -> SmartPointerSendable<*mut libobs::obs_source_t> {
204        self.source.clone()
205    }
206}
207
208impl ObsSourceTrait for ObsSourceRef {
209    fn signals(&self) -> &Arc<ObsSourceSignals> {
210        &self.signal_manager
211    }
212
213    fn get_active_filters(&self) -> Result<Vec<ObsFilterGuardPair>, ObsError> {
214        let guard = self.attached_filters.read().map_err(|_| {
215            ObsError::LockError("Failed to acquire read lock on attached filters".into())
216        })?;
217
218        Ok(guard.clone())
219    }
220
221    fn apply_filter(&self, filter: &ObsFilterRef) -> Result<(), ObsError> {
222        let mut guard = self.attached_filters.write().map_err(|_| {
223            ObsError::LockError("Failed to acquire write lock on attached filters".into())
224        })?;
225
226        let source_ptr = self.as_ptr();
227        let filter_ptr = filter.as_ptr();
228
229        let has_filter = guard
230            .iter()
231            .any(|f| f.get_inner().as_ptr().get_ptr() == filter.as_ptr().get_ptr());
232
233        if has_filter {
234            return Err(ObsError::FilterAlreadyApplied);
235        }
236
237        run_with_obs!(self.runtime(), (source_ptr, filter_ptr), move || unsafe {
238            // Safety: Both pointers are valid because of the smart pointers.
239            libobs::obs_source_filter_add(source_ptr.get_ptr(), filter_ptr.get_ptr());
240            Ok(())
241        })??;
242
243        let runtime = self.runtime().clone();
244        let drop_guard = _ObsRemoveFilterOnDrop::new(self.as_ptr(), filter.as_ptr(), None, runtime);
245
246        guard.push(ObsFilterGuardPair::new(
247            filter.clone(),
248            Arc::new(drop_guard),
249        ));
250
251        Ok(())
252    }
253}
254
255impl_signal_manager!(|ptr: SmartPointerSendable<*mut libobs::obs_source_t>| unsafe {
256    // Safety: We are using a smart pointer, so it is fine
257    libobs::obs_source_get_signal_handler(ptr.get_ptr())
258}, ObsSourceSignals for *mut libobs::obs_source_t, [
259    "destroy": {},
260    "remove": {},
261    "update": {},
262    "save": {},
263    "load": {},
264    "activate": {},
265    "deactivate": {},
266    "show": {},
267    "hide": {},
268    "mute": { struct MuteSignal {
269        muted: bool
270    } },
271    "push_to_mute_changed": {struct PushToMuteChangedSignal {
272        enabled: bool
273    }},
274    "push_to_mute_delay": {struct PushToMuteDelaySignal {
275        delay: i64
276    }},
277    "push_to_talk_changed": {struct PushToTalkChangedSignal {
278        enabled: bool
279    }},
280    "push_to_talk_delay": {struct PushToTalkDelaySignal {
281        delay: i64
282    }},
283    "enable": {struct EnableSignal {
284        enabled: bool
285    }},
286    "rename": {struct NewNameSignal {
287        new_name: String,
288        prev_name: String
289    }},
290    "update_properties": {},
291    "update_flags": {struct UpdateFlagsSignal {
292        flags: i64
293    }},
294    "audio_sync": {struct AudioSyncSignal {
295        offset: i64,
296    }},
297    "audio_balance": {struct AudioBalanceSignal {
298        balance: f64,
299    }},
300    "audio_mixers": {struct AudioMixersSignal {
301        mixers: i64,
302    }},
303    "audio_activate": {},
304    "audio_deactivate": {},
305    "filter_add": {struct FilterAddSignal {
306        POINTERS {
307            filter: *mut libobs::obs_source_t,
308        }
309    }},
310    "filter_remove": {struct FilterRemoveSignal {
311        POINTERS {
312            filter: *mut libobs::obs_source_t,
313        }
314    }},
315    "reorder_filters": {},
316    "transition_start": {},
317    "transition_video_stop": {},
318    "transition_stop": {},
319    "media_started": {},
320    "media_ended":{},
321    "media_pause": {},
322    "media_play": {},
323    "media_restart": {},
324    "media_stopped": {},
325    "media_next": {},
326    "media_previous": {},
327]);
328
329#[derive(Debug)]
330struct _ObsSourceGuard {
331    source: Sendable<*mut obs_source_t>,
332    runtime: ObsRuntime,
333}
334
335impl ObsDropGuard for _ObsSourceGuard {}
336
337impl_obs_drop!(_ObsSourceGuard, (source), move || unsafe {
338    // Safety: We are in the runtime and the pointer is valid because of the drop guard
339    libobs::obs_source_release(source.0);
340});