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 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 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 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 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 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 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 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 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 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 libobs::obs_sceneitem_remove(scene_item_ptr);
213 libobs::obs_sceneitem_release(scene_item_ptr);
215 })?;
216
217 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 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 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 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 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 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 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 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 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 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 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 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]);