libobs_wrapper\scenes/
mod.rs

1mod transform_info;
2pub use transform_info::*;
3
4use std::collections::{HashMap, HashSet};
5use std::fmt::Debug;
6use std::hash::Hash;
7use std::sync::{Arc, RwLock};
8
9use getters0::Getters;
10use libobs::{obs_scene_item, obs_scene_t, obs_source_t, obs_transform_info, obs_video_info};
11
12use crate::enums::ObsBoundsType;
13use crate::macros::impl_eq_of_ptr;
14use crate::unsafe_send::SendableComp;
15use crate::{
16    graphics::Vec2,
17    impl_obs_drop, impl_signal_manager, run_with_obs,
18    runtime::ObsRuntime,
19    sources::{ObsFilterRef, ObsSourceRef},
20    unsafe_send::Sendable,
21    utils::{ObsError, ObsString, SourceInfo},
22};
23
24#[derive(Debug)]
25struct _SceneDropGuard {
26    scene: Sendable<*mut obs_scene_t>,
27    runtime: ObsRuntime,
28}
29
30impl_obs_drop!(_SceneDropGuard, (scene), move || unsafe {
31    let scene_source = libobs::obs_scene_get_source(scene);
32
33    libobs::obs_source_release(scene_source);
34    libobs::obs_scene_release(scene);
35});
36
37#[derive(Debug, Clone, Getters)]
38#[skip_new]
39pub struct ObsSceneRef {
40    #[skip_getter]
41    pub(crate) scene: Arc<Sendable<*mut obs_scene_t>>,
42    name: ObsString,
43    #[get_mut]
44    pub(crate) sources: Arc<RwLock<HashSet<ObsSourceRef>>>,
45    #[skip_getter]
46    /// Maps the currently current active scenes by their channel (this is a shared reference between all scenes)
47    pub(crate) active_scenes: Arc<RwLock<HashMap<u32, ObsSceneRef>>>,
48
49    #[skip_getter]
50    _guard: Arc<_SceneDropGuard>,
51
52    #[skip_getter]
53    runtime: ObsRuntime,
54
55    pub(crate) signals: Arc<ObsSceneSignals>,
56}
57
58impl_eq_of_ptr!(ObsSceneRef, scene);
59
60impl ObsSceneRef {
61    pub(crate) fn new(
62        name: ObsString,
63        active_scenes: Arc<RwLock<HashMap<u32, ObsSceneRef>>>,
64        runtime: ObsRuntime,
65    ) -> Result<Self, ObsError> {
66        let name_ptr = name.as_ptr();
67        let scene = run_with_obs!(runtime, (name_ptr), move || unsafe {
68            Sendable(libobs::obs_scene_create(name_ptr))
69        })?;
70
71        let signals = Arc::new(ObsSceneSignals::new(&scene, runtime.clone())?);
72        Ok(Self {
73            name,
74            scene: Arc::new(scene.clone()),
75            sources: Arc::new(RwLock::new(HashSet::new())),
76            active_scenes,
77            _guard: Arc::new(_SceneDropGuard {
78                scene,
79                runtime: runtime.clone(),
80            }),
81            runtime,
82            signals,
83        })
84    }
85
86    #[deprecated = "Use ObsSceneRef::set_to_channel instead"]
87    pub fn add_and_set(&self, channel: u32) -> Result<(), ObsError> {
88        self.set_to_channel(channel)
89    }
90
91    /// Sets this scene to a given output channel.
92    /// There are 64
93    /// channels that you can assign scenes to, which will draw on top of each
94    /// other in ascending index order.
95    pub fn set_to_channel(&self, channel: u32) -> Result<(), ObsError> {
96        if channel >= libobs::MAX_CHANNELS {
97            return Err(ObsError::InvalidOperation(format!(
98                "Channel {} is out of bounds (max {})",
99                channel,
100                libobs::MAX_CHANNELS - 1
101            )));
102        }
103
104        // let mut s = self
105        //     .active_scenes
106        //     .write()
107        //     .map_err(|e| ObsError::LockError(format!("{:?}", e)))?;
108
109        // s.insert(channel, self.clone());
110
111        let scene_source_ptr = self.get_scene_source_ptr()?;
112        run_with_obs!(self.runtime, (scene_source_ptr), move || unsafe {
113            libobs::obs_set_output_source(channel, scene_source_ptr);
114        })
115    }
116
117    /// Removes a scene from a given output channel, for more info about channels see `set_to_channel`.
118    pub fn remove_from_channel(&self, channel: u32) -> Result<(), ObsError> {
119        if channel >= libobs::MAX_CHANNELS {
120            return Err(ObsError::InvalidOperation(format!(
121                "Channel {} is out of bounds (max {})",
122                channel,
123                libobs::MAX_CHANNELS - 1
124            )));
125        }
126
127        let mut s = self
128            .active_scenes
129            .write()
130            .map_err(|e| ObsError::LockError(format!("{:?}", e)))?;
131
132        s.remove(&channel);
133
134        run_with_obs!(self.runtime, (), move || unsafe {
135            libobs::obs_set_output_source(channel, std::ptr::null_mut());
136        })
137    }
138
139    /// Gets the underlying source pointer of this scene, which is used internally when setting it to a channel.
140    pub fn get_scene_source_ptr(&self) -> Result<Sendable<*mut obs_source_t>, ObsError> {
141        let scene_ptr = self.scene.clone();
142        run_with_obs!(self.runtime, (scene_ptr), move || unsafe {
143            Sendable(libobs::obs_scene_get_source(scene_ptr))
144        })
145    }
146
147    /// Adds and creates the specified source to this scene. Returns a reference to the created source. The source is also stored internally in this scene.
148    ///
149    /// If you need to remove the source later, use `remove_source`.
150    pub fn add_source(&mut self, info: SourceInfo) -> Result<ObsSourceRef, ObsError> {
151        let source = ObsSourceRef::new(
152            info.id,
153            info.name,
154            info.settings,
155            info.hotkey_data,
156            self.runtime.clone(),
157        )?;
158
159        let scene_ptr = self.scene.clone();
160        let source_ptr = source.source.clone();
161
162        let ptr = run_with_obs!(self.runtime, (scene_ptr, source_ptr), move || unsafe {
163            Sendable(libobs::obs_scene_add(scene_ptr, source_ptr))
164        })?;
165
166        if ptr.0.is_null() {
167            return Err(ObsError::NullPointer);
168        }
169
170        //TODO We should clear one reference because with this obs doesn't clean up properly
171        source
172            .scene_items
173            .write()
174            .map_err(|e| ObsError::LockError(format!("{:?}", e)))?
175            .insert(SendableComp(self.scene.0), ptr.clone());
176
177        self.sources
178            .write()
179            .map_err(|e| ObsError::LockError(format!("{:?}", e)))?
180            .insert(source.clone());
181        Ok(source)
182    }
183
184    /// Gets a source by name from this scene. Returns None if no source with the given name exists in this scene.
185    pub fn get_source_mut(&self, name: &str) -> Result<Option<ObsSourceRef>, ObsError> {
186        let r = self
187            .sources
188            .read()
189            .map_err(|e| ObsError::LockError(format!("{:?}", e)))?
190            .iter()
191            .find(|s| s.name() == name)
192            .cloned();
193
194        Ok(r)
195    }
196
197    /// Removes the given source from this scene. Removes the corresponding scene item as well. It may be possible that this source is still added to another scene.
198    pub fn remove_source(&mut self, source: &ObsSourceRef) -> Result<(), ObsError> {
199        let scene_items = source
200            .scene_items
201            .read()
202            .map_err(|e| ObsError::LockError(format!("{:?}", e)))?;
203
204        let sendable_comp = SendableComp(self.scene.0);
205        let scene_item_ptr = scene_items
206            .get(&sendable_comp)
207            .ok_or(ObsError::SourceNotFound)?
208            .clone();
209
210        run_with_obs!(self.runtime, (scene_item_ptr), move || unsafe {
211            // Remove the scene item
212            libobs::obs_sceneitem_remove(scene_item_ptr);
213            // Release the scene item reference
214            libobs::obs_sceneitem_release(scene_item_ptr);
215        })?;
216
217        // We need to make sure to remove references from both the scene and the source
218        self.sources
219            .write()
220            .map_err(|e| ObsError::LockError(format!("{:?}", e)))?
221            .remove(source);
222
223        source
224            .scene_items
225            .write()
226            .map_err(|e| ObsError::LockError(format!("{:?}", e)))?
227            .remove(&sendable_comp);
228
229        Ok(())
230    }
231
232    /// Adds a filter to the given source in this scene.
233    pub fn add_scene_filter(
234        &self,
235        source: &ObsSourceRef,
236        filter_ref: &ObsFilterRef,
237    ) -> Result<(), ObsError> {
238        let source_ptr = source.source.clone();
239        let filter_ptr = filter_ref.source.clone();
240        run_with_obs!(self.runtime, (source_ptr, filter_ptr), move || unsafe {
241            libobs::obs_source_filter_add(source_ptr, filter_ptr);
242        })?;
243        Ok(())
244    }
245
246    /// Removes a filter from the this scene (internally removes the filter to the scene's source).
247    pub fn remove_scene_filter(
248        &self,
249        source: &ObsSourceRef,
250        filter_ref: &ObsFilterRef,
251    ) -> Result<(), ObsError> {
252        let source_ptr = source.source.clone();
253        let filter_ptr = filter_ref.source.clone();
254        run_with_obs!(self.runtime, (source_ptr, filter_ptr), move || unsafe {
255            libobs::obs_source_filter_remove(source_ptr, filter_ptr);
256        })?;
257        Ok(())
258    }
259
260    /// Gets the underlying scene item pointer for the given source in this scene.
261    ///
262    /// A scene item is basically the representation of a source within this scene. It holds information about the position, scale, rotation, etc.
263    pub fn get_scene_item_ptr(
264        &self,
265        source: &ObsSourceRef,
266    ) -> Result<Sendable<*mut obs_scene_item>, ObsError> {
267        let scene_items = source
268            .scene_items
269            .read()
270            .map_err(|e| ObsError::LockError(format!("{:?}", e)))?;
271
272        let sendable_comp = SendableComp(self.scene.0);
273        let scene_item_ptr = scene_items
274            .get(&sendable_comp)
275            .ok_or(ObsError::SourceNotFound)?
276            .clone();
277
278        Ok(scene_item_ptr)
279    }
280
281    /// Gets the transform info of the given source in this scene.
282    pub fn get_transform_info(&self, source: &ObsSourceRef) -> Result<ObsTransformInfo, ObsError> {
283        let scene_item_ptr = self.get_scene_item_ptr(source)?;
284
285        let item_info = run_with_obs!(self.runtime, (scene_item_ptr), move || unsafe {
286            let mut item_info: obs_transform_info = std::mem::zeroed();
287            libobs::obs_sceneitem_get_info2(scene_item_ptr, &mut item_info);
288            ObsTransformInfo(item_info)
289        })?;
290
291        Ok(item_info)
292    }
293
294    /// Gets the position of the given source in this scene.
295    pub fn get_source_position(&self, source: &ObsSourceRef) -> Result<Vec2, ObsError> {
296        let scene_item_ptr = self.get_scene_item_ptr(source)?;
297
298        let position = run_with_obs!(self.runtime, (scene_item_ptr), move || unsafe {
299            let mut main_pos: libobs::vec2 = std::mem::zeroed();
300            libobs::obs_sceneitem_get_pos(scene_item_ptr, &mut main_pos);
301            Vec2::from(main_pos)
302        })?;
303
304        Ok(position)
305    }
306
307    /// Gets the scale of the given source in this scene.
308    pub fn get_source_scale(&self, source: &ObsSourceRef) -> Result<Vec2, ObsError> {
309        let scene_item_ptr = self.get_scene_item_ptr(source)?;
310
311        let scale = run_with_obs!(self.runtime, (scene_item_ptr), move || unsafe {
312            let mut main_pos: libobs::vec2 = std::mem::zeroed();
313            libobs::obs_sceneitem_get_scale(scene_item_ptr, &mut main_pos);
314            Vec2::from(main_pos)
315        })?;
316
317        Ok(scale)
318    }
319
320    /// Sets the position of the given source in this scene.
321    pub fn set_source_position(
322        &self,
323        source: &ObsSourceRef,
324        position: Vec2,
325    ) -> Result<(), ObsError> {
326        let scene_item_ptr = self.get_scene_item_ptr(source)?;
327
328        run_with_obs!(self.runtime, (scene_item_ptr), move || unsafe {
329            libobs::obs_sceneitem_set_pos(scene_item_ptr, &position.into());
330        })?;
331
332        Ok(())
333    }
334
335    /// Sets the scale of the given source in this scene.
336    pub fn set_source_scale(&self, source: &ObsSourceRef, scale: Vec2) -> Result<(), ObsError> {
337        let scene_item_ptr = self.get_scene_item_ptr(source)?;
338
339        run_with_obs!(self.runtime, (scene_item_ptr), move || unsafe {
340            libobs::obs_sceneitem_set_scale(scene_item_ptr, &scale.into());
341        })?;
342
343        Ok(())
344    }
345
346    /// Sets the transform info of the given source in this scene.
347    /// The `ObsTransformInfo` can be built by using the `ObsTransformInfoBuilder`.
348    pub fn set_transform_info(
349        &self,
350        source: &ObsSourceRef,
351        info: &ObsTransformInfo,
352    ) -> Result<(), ObsError> {
353        let scene_item_ptr = self.get_scene_item_ptr(source)?;
354
355        let item_info = Sendable(info.clone());
356        run_with_obs!(self.runtime, (scene_item_ptr, item_info), move || unsafe {
357            libobs::obs_sceneitem_set_info2(scene_item_ptr, &item_info.0);
358        })?;
359
360        Ok(())
361    }
362
363    /// Fits the given source to the screen size.
364    /// If the source is locked, no action is taken.
365    ///
366    /// Returns `Ok(true)` if the source was resized, `Ok(false)` if the source was locked and not resized.
367    pub fn fit_source_to_screen(&self, source: &ObsSourceRef) -> Result<bool, ObsError> {
368        let scene_item_ptr = self.get_scene_item_ptr(source)?;
369
370        let is_locked = {
371            run_with_obs!(self.runtime, (scene_item_ptr), move || unsafe {
372                libobs::obs_sceneitem_locked(scene_item_ptr)
373            })?
374        };
375
376        if is_locked {
377            return Ok(false);
378        }
379
380        let ovi = run_with_obs!(self.runtime, (), move || unsafe {
381            let mut ovi = std::mem::MaybeUninit::<obs_video_info>::uninit();
382            libobs::obs_get_video_info(ovi.as_mut_ptr());
383
384            Sendable(ovi.assume_init())
385        })?;
386
387        let bounds_crop = run_with_obs!(self.runtime, (scene_item_ptr), move || unsafe {
388            libobs::obs_sceneitem_get_bounds_crop(scene_item_ptr)
389        })?;
390
391        // We are not constructing it from the source here because we want to reset full transform (so we use build instead of build_with_fallback)
392        let item_info = ObsTransformInfoBuilder::new()
393            .set_bounds_type(ObsBoundsType::ScaleInner)
394            .set_crop_to_bounds(bounds_crop)
395            .build(ovi.0.base_width, ovi.0.base_height);
396
397        self.set_transform_info(source, &item_info)?;
398        Ok(true)
399    }
400
401    pub fn as_ptr(&self) -> Sendable<*mut obs_scene_t> {
402        Sendable(self.scene.0)
403    }
404}
405
406impl_signal_manager!(|scene_ptr| unsafe {
407    let source_ptr = libobs::obs_scene_get_source(scene_ptr);
408
409    libobs::obs_source_get_signal_handler(source_ptr)
410}, ObsSceneSignals for ObsSceneRef<*mut obs_scene_t>, [
411    "item_add": {
412        struct ItemAddSignal {
413            POINTERS {
414                item: *mut libobs::obs_sceneitem_t,
415            }
416        }
417    },
418    "item_remove": {
419        struct ItemRemoveSignal {
420            POINTERS {
421                item: *mut libobs::obs_sceneitem_t,
422            }
423        }
424    },
425    "reorder": {},
426    "refresh": {},
427    "item_visible": {
428        struct ItemVisibleSignal {
429            visible: bool;
430            POINTERS {
431                item: *mut libobs::obs_sceneitem_t,
432            }
433        }
434    },
435    "item_locked": {
436        struct ItemLockedSignal {
437            locked: bool;
438            POINTERS {
439                item: *mut libobs::obs_sceneitem_t,
440            }
441        }
442    },
443    "item_select": {
444        struct ItemSelectSignal {
445            POINTERS {
446                item: *mut libobs::obs_sceneitem_t,
447            }
448        }
449    },
450    "item_deselect": {
451        struct ItemDeselectSignal {
452            POINTERS {
453                item: *mut libobs::obs_sceneitem_t,
454            }
455        }
456    },
457    "item_transform": {
458        struct ItemTransformSignal {
459            POINTERS {
460                item: *mut libobs::obs_sceneitem_t,
461            }
462        }
463    }
464]);