1use std::{
40 collections::HashMap,
41 ffi::CStr,
42 sync::{Arc, Mutex, RwLock},
43 thread::ThreadId,
44};
45
46#[cfg(target_os = "linux")]
47use crate::utils::initialization::PlatformType;
48use crate::{
49 data::{
50 object::ObsObjectTrait,
51 output::{ObsOutputTrait, ObsOutputTraitSealed, ObsReplayBufferOutputRef},
52 },
53 display::{ObsDisplayCreationData, ObsDisplayRef},
54};
55use crate::{
56 data::{output::ObsOutputRef, video::ObsVideoInfo, ObsData},
57 enums::{ObsLogLevel, ObsResetVideoStatus},
58 logger::LOGGER,
59 run_with_obs,
60 runtime::ObsRuntime,
61 scenes::ObsSceneRef,
62 sources::{ObsFilterRef, ObsSourceBuilder},
63 unsafe_send::Sendable,
64 utils::{FilterInfo, ObsError, ObsModules, ObsString, OutputInfo, StartupInfo},
65};
66use getters0::Getters;
67use libobs::{audio_output, video_output};
68
69lazy_static::lazy_static! {
70 pub(crate) static ref OBS_THREAD_ID: Mutex<Option<ThreadId>> = Mutex::new(None);
71}
72
73pub(crate) type GeneralStorage<T> = Arc<RwLock<Vec<Arc<Box<T>>>>>;
74
75#[derive(Debug, Getters, Clone)]
85#[skip_new]
86pub struct ObsContext {
87 startup_info: Arc<RwLock<StartupInfo>>,
91 #[get_mut]
92 displays: Arc<RwLock<HashMap<usize, ObsDisplayRef>>>,
94
95 #[allow(dead_code)]
98 #[get_mut]
99 outputs: GeneralStorage<dyn ObsOutputTrait>,
100
101 #[get_mut]
102 scenes: Arc<RwLock<Vec<ObsSceneRef>>>,
103
104 #[get_mut]
106 filters: Arc<RwLock<Vec<ObsFilterRef>>>,
107
108 #[skip_getter]
109 _obs_modules: Arc<ObsModules>,
110
111 runtime: ObsRuntime,
115
116 #[cfg(target_os = "linux")]
117 glib_loop: Arc<RwLock<Option<crate::utils::linux::LinuxGlibLoop>>>,
118}
119
120impl ObsContext {
121 pub fn check_version_compatibility() -> bool {
124 unsafe {
126 #[allow(unknown_lints)]
127 #[allow(ensure_obs_call_in_runtime)]
128 let version = libobs::obs_get_version_string();
129 if version.is_null() {
130 return false;
131 }
132
133 let version_str = match CStr::from_ptr(version).to_str() {
134 Ok(s) => s,
135 Err(_) => return false,
136 };
137
138 let version_parts: Vec<&str> = version_str.split('.').collect();
139 if version_parts.len() != 3 {
140 return false;
141 }
142
143 let major = match version_parts[0].parse::<u64>() {
144 Ok(v) => v,
145 Err(_) => return false,
146 };
147
148 major == libobs::LIBOBS_API_MAJOR_VER as u64
149 }
150 }
151
152 pub fn builder() -> StartupInfo {
153 StartupInfo::new()
154 }
155
156 pub fn new(info: StartupInfo) -> Result<ObsContext, ObsError> {
174 log::trace!("Getting version number...");
175
176 #[allow(unknown_lints)]
177 #[allow(ensure_obs_call_in_runtime)]
178 let version_numb = unsafe { libobs::obs_get_version() };
181 if version_numb == 0 {
182 return Err(ObsError::InvalidDll);
183 }
184
185 let (runtime, obs_modules, info) = ObsRuntime::startup(info)?;
187 #[cfg(target_os = "linux")]
188 let linux_opt = if info.start_glib_loop {
189 Some(crate::utils::linux::LinuxGlibLoop::new())
190 } else {
191 None
192 };
193
194 Ok(Self {
195 _obs_modules: Arc::new(obs_modules),
196 displays: Default::default(),
197 outputs: Default::default(),
198 scenes: Default::default(),
199 filters: Default::default(),
200 runtime: runtime.clone(),
201 startup_info: Arc::new(RwLock::new(info)),
202 #[cfg(target_os = "linux")]
203 glib_loop: Arc::new(RwLock::new(linux_opt)),
204 })
205 }
206
207 #[cfg(target_os = "linux")]
208 pub fn get_platform(&self) -> Result<PlatformType, ObsError> {
209 self.runtime.get_platform()
210 }
211
212 pub fn get_version(&self) -> Result<String, ObsError> {
213 Self::get_version_global()
214 }
215
216 pub fn get_version_global() -> Result<String, ObsError> {
217 unsafe {
218 #[allow(unknown_lints)]
219 #[allow(ensure_obs_call_in_runtime)]
220 let version = libobs::obs_get_version_string();
222 let version_cstr = CStr::from_ptr(version);
223
224 let version = version_cstr.to_string_lossy().into_owned();
225
226 Ok(version)
227 }
228 }
229
230 pub fn log(&self, level: ObsLogLevel, msg: &str) {
231 let mut log = LOGGER.lock().unwrap();
232 log.log(level, msg.to_string());
233 }
234
235 pub fn reset_video(&mut self, ovi: ObsVideoInfo) -> Result<(), ObsError> {
250 if self
253 .startup_info
254 .read()
255 .map_err(|_| {
256 ObsError::LockError("Failed to acquire read lock on startup info".to_string())
257 })?
258 .obs_video_info
259 .graphics_module()
260 != ovi.graphics_module()
261 {
262 return Err(ObsError::ResetVideoFailureGraphicsModule);
263 }
264
265 let has_active_outputs = {
266 self.outputs
267 .read()
268 .map_err(|_| {
269 ObsError::LockError("Failed to acquire read lock on outputs".to_string())
270 })?
271 .iter()
272 .any(|output| output.is_active().unwrap_or_default())
273 };
274
275 if has_active_outputs {
276 return Err(ObsError::ResetVideoFailureOutputActive);
277 }
278
279 let vid_ptr = Sendable(ovi.as_ptr());
286 let reset_video_status = run_with_obs!(self.runtime, (vid_ptr), move || unsafe {
287 libobs::obs_reset_video(vid_ptr.0)
289 })?;
290
291 let reset_video_status = num_traits::FromPrimitive::from_i32(reset_video_status);
292
293 let reset_video_status = match reset_video_status {
294 Some(x) => x,
295 None => ObsResetVideoStatus::Failure,
296 };
297
298 if reset_video_status == ObsResetVideoStatus::Success {
299 self.startup_info
300 .write()
301 .map_err(|_| {
302 ObsError::LockError("Failed to acquire write lock on startup info".to_string())
303 })?
304 .obs_video_info = ovi;
305
306 Ok(())
307 } else {
308 Err(ObsError::ResetVideoFailure(reset_video_status))
309 }
310 }
311
312 pub unsafe fn get_video_ptr(&self) -> Result<Sendable<*mut video_output>, ObsError> {
317 run_with_obs!(self.runtime, || unsafe {
319 Sendable(libobs::obs_get_video())
321 })
322 }
323
324 pub unsafe fn get_audio_ptr(&self) -> Result<Sendable<*mut audio_output>, ObsError> {
329 run_with_obs!(self.runtime, || unsafe {
331 Sendable(libobs::obs_get_audio())
333 })
334 }
335
336 pub fn data(&self) -> Result<ObsData, ObsError> {
337 ObsData::new(self.runtime.clone())
338 }
339
340 pub fn replay_buffer(
341 &mut self,
342 info: OutputInfo,
343 ) -> Result<ObsReplayBufferOutputRef, ObsError> {
344 let output = ObsReplayBufferOutputRef::new(info, self.runtime.clone());
345
346 match output {
347 Ok(x) => {
348 let tmp = x.clone();
349 self.outputs
350 .write()
351 .map_err(|_| {
352 ObsError::LockError("Failed to acquire write lock on outputs".to_string())
353 })?
354 .push(Arc::new(Box::new(x)));
355 Ok(tmp)
356 }
357
358 Err(x) => Err(x),
359 }
360 }
361
362 pub fn output(&mut self, info: OutputInfo) -> Result<ObsOutputRef, ObsError> {
363 let output = ObsOutputRef::new(info, self.runtime.clone());
364
365 match output {
366 Ok(x) => {
367 let tmp = x.clone();
368 self.outputs
369 .write()
370 .map_err(|_| {
371 ObsError::LockError("Failed to acquire write lock on outputs".to_string())
372 })?
373 .push(Arc::new(Box::new(x)));
374 Ok(tmp)
375 }
376
377 Err(x) => Err(x),
378 }
379 }
380
381 pub fn obs_filter(&mut self, info: FilterInfo) -> Result<ObsFilterRef, ObsError> {
382 let filter = ObsFilterRef::new(
383 info.id,
384 info.name,
385 info.settings,
386 info.hotkey_data,
387 self.runtime.clone(),
388 );
389
390 match filter {
391 Ok(x) => {
392 let tmp = x.clone();
393 self.filters
394 .write()
395 .map_err(|_| {
396 ObsError::LockError("Failed to acquire write lock on filters".to_string())
397 })?
398 .push(x);
399 Ok(tmp)
400 }
401
402 Err(x) => Err(x),
403 }
404 }
405
406 #[cfg(not(target_os = "linux"))]
414 pub fn display(&mut self, data: ObsDisplayCreationData) -> Result<ObsDisplayRef, ObsError> {
415 self.inner_display_fn(data)
416 }
417
418 #[cfg(target_os = "linux")]
430 pub unsafe fn display(
431 &mut self,
432 data: ObsDisplayCreationData,
433 ) -> Result<ObsDisplayRef, ObsError> {
434 self.inner_display_fn(data)
435 }
436
437 fn inner_display_fn(
439 &mut self,
440 data: ObsDisplayCreationData,
441 ) -> Result<ObsDisplayRef, ObsError> {
442 #[cfg(target_os = "linux")]
443 {
444 let nix_display = self
447 .startup_info
448 .read()
449 .map_err(|_| {
450 ObsError::LockError("Failed to acquire read lock on startup info".to_string())
451 })?
452 .nix_display
453 .clone();
454
455 let is_wayland_handle = data.window_handle.is_wayland;
456 if is_wayland_handle && nix_display.is_none() {
457 return Err(ObsError::DisplayCreationError(
458 "Wayland window handle provided but no NixDisplay was set in StartupInfo."
459 .to_string(),
460 ));
461 }
462
463 if let Some(nix_display) = &nix_display {
464 if is_wayland_handle {
465 match nix_display {
466 crate::utils::NixDisplay::X11(_display) => {
467 return Err(ObsError::DisplayCreationError(
468 "Provided NixDisplay is X11, but the window handle is Wayland."
469 .to_string(),
470 ));
471 }
472 crate::utils::NixDisplay::Wayland(display) => {
473 use crate::utils::linux::wl_proxy_get_display;
474 if !data.window_handle.is_wayland {
475 return Err(ObsError::DisplayCreationError(
476 "Provided window handle is not a Wayland handle, but the NixDisplay is Wayland.".to_string(),
477 ));
478 }
479
480 let surface_handle = data.window_handle.window.0.display;
481 let display_from_surface = unsafe {
482 wl_proxy_get_display(surface_handle)
484 };
485 if let Err(e) = display_from_surface {
486 log::warn!("Could not get display from surface handle on wayland. Make sure your wayland client is at least version 1.23. Error: {:?}", e);
487 } else {
488 let display_from_surface = display_from_surface.unwrap();
489 if display_from_surface != display.0 {
490 return Err(ObsError::DisplayCreationError(
491 "Provided surface handle's Wayland display does not match the NixDisplay's Wayland display.".to_string(),
492 ));
493 }
494 }
495 }
496 }
497 }
498 }
499 }
500
501 let display = ObsDisplayRef::new(data, self.runtime.clone())
502 .map_err(|e| ObsError::DisplayCreationError(e.to_string()))?;
503
504 let id = display.id();
505 self.displays
506 .write()
507 .map_err(|_| {
508 ObsError::LockError("Failed to acquire write lock on displays".to_string())
509 })?
510 .insert(id, display.clone());
511
512 Ok(display)
513 }
514
515 pub fn remove_display(&mut self, display: &ObsDisplayRef) -> Result<(), ObsError> {
516 self.remove_display_by_id(display.id())
517 }
518
519 pub fn remove_display_by_id(&mut self, id: usize) -> Result<(), ObsError> {
520 self.displays
521 .write()
522 .map_err(|_| {
523 ObsError::LockError("Failed to acquire write lock on displays".to_string())
524 })?
525 .remove(&id);
526
527 Ok(())
528 }
529
530 pub fn get_display_by_id(&self, id: usize) -> Result<Option<ObsDisplayRef>, ObsError> {
531 let d = self
532 .displays
533 .read()
534 .map_err(|_| {
535 ObsError::LockError("Failed to acquire read lock on displays".to_string())
536 })?
537 .get(&id)
538 .cloned();
539
540 Ok(d)
541 }
542
543 pub fn get_output(
544 &mut self,
545 name: &str,
546 ) -> Result<Option<Arc<Box<dyn ObsOutputTrait>>>, ObsError> {
547 let o = self
548 .outputs
549 .read()
550 .map_err(|_| ObsError::LockError("Failed to acquire read lock on outputs".to_string()))?
551 .iter()
552 .find(|x| x.name().to_string().as_str() == name)
553 .cloned();
554
555 Ok(o)
556 }
557
558 pub fn update_output(&mut self, name: &str, settings: ObsData) -> Result<(), ObsError> {
559 match self
560 .outputs
561 .read()
562 .map_err(|_| ObsError::LockError("Failed to acquire read lock on outputs".to_string()))?
563 .iter()
564 .find(|x| x.name().to_string().as_str() == name)
565 {
566 Some(output) => output.update_settings(settings),
567 None => Err(ObsError::OutputNotFound),
568 }
569 }
570
571 pub fn get_filter(&mut self, name: &str) -> Result<Option<ObsFilterRef>, ObsError> {
572 let f = self
573 .filters
574 .read()
575 .map_err(|_| ObsError::LockError("Failed to acquire read lock on filters".to_string()))?
576 .iter()
577 .find(|x| x.name().to_string().as_str() == name)
578 .cloned();
579
580 Ok(f)
581 }
582
583 pub fn scene<T: Into<ObsString> + Send + Sync>(
598 &mut self,
599 name: T,
600 channel: Option<u32>,
601 ) -> Result<ObsSceneRef, ObsError> {
602 let scene = ObsSceneRef::new(name.into(), self.runtime.clone())?;
603
604 let tmp = scene.clone();
605 self.scenes
606 .write()
607 .map_err(|_| ObsError::LockError("Failed to acquire write lock on scenes".to_string()))?
608 .push(scene);
609
610 if let Some(channel) = channel {
611 tmp.set_to_channel(channel)?;
612 }
613 Ok(tmp)
614 }
615
616 pub fn get_scene(&mut self, name: &str) -> Result<Option<ObsSceneRef>, ObsError> {
617 let r = self
618 .scenes
619 .read()
620 .map_err(|_| ObsError::LockError("Failed to acquire read lock on scenes".to_string()))?
621 .iter()
622 .find(|x| x.name().to_string().as_str() == name)
623 .cloned();
624
625 Ok(r)
626 }
627
628 pub fn source_builder<T: ObsSourceBuilder, K: Into<ObsString> + Send + Sync>(
629 &self,
630 name: K,
631 ) -> Result<T, ObsError> {
632 T::new(name.into(), self.runtime.clone())
633 }
634}