1mod registry;
40
41use std::{
42 ffi::CStr,
43 sync::{Arc, Condvar, Mutex, RwLock},
44 thread::ThreadId,
45};
46
47#[cfg(target_os = "linux")]
48use crate::utils::initialization::PlatformType;
49use crate::{
50 data::output::{ObsOutputTrait, ObsOutputTraitSealed, ObsReplayBufferOutputRef},
51 display::{ObsDisplayCreationData, ObsDisplayRef},
52};
53use crate::{
54 data::{output::ObsOutputRef, video::ObsVideoInfo, ObsData},
55 enums::{ObsLogLevel, ObsResetVideoStatus},
56 logger::LOGGER,
57 run_with_obs,
58 runtime::ObsRuntime,
59 scenes::ObsSceneRef,
60 sources::{ObsFilterRef, ObsSourceBuilder},
61 unsafe_send::Sendable,
62 utils::{FilterInfo, ObsError, ObsModules, ObsString, OutputInfo, StartupInfo},
63};
64use getters0::Getters;
65use libobs::{audio_output, video_output};
66use registry::ObjectRegistry;
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub(crate) enum ObsRuntimeState {
70 Idle,
71 Starting,
72 Active(ThreadId),
73 ShuttingDown,
74}
75
76lazy_static::lazy_static! {
77 static ref OBS_RUNTIME_STATE: (Mutex<ObsRuntimeState>, Condvar) =
78 (Mutex::new(ObsRuntimeState::Idle), Condvar::new());
79}
80
81pub(crate) fn reserve_runtime_slot() -> Result<(), ObsError> {
82 let (lock, changed) = &*OBS_RUNTIME_STATE;
83 let mut state = lock.lock().map_err(|_| ObsError::MutexFailure)?;
84 loop {
85 match *state {
86 ObsRuntimeState::Idle => {
87 *state = ObsRuntimeState::Starting;
88 return Ok(());
89 }
90 ObsRuntimeState::ShuttingDown => {
91 state = changed.wait(state).map_err(|_| ObsError::MutexFailure)?;
92 }
93 ObsRuntimeState::Starting | ObsRuntimeState::Active(_) => {
94 return Err(ObsError::ThreadFailure);
95 }
96 }
97 }
98}
99
100pub(crate) fn cancel_runtime_start() {
101 let (lock, changed) = &*OBS_RUNTIME_STATE;
102 let mut state = match lock.lock() {
103 Ok(state) => state,
104 Err(poisoned) => poisoned.into_inner(),
105 };
106 if *state == ObsRuntimeState::Starting {
107 *state = ObsRuntimeState::Idle;
108 changed.notify_all();
109 }
110}
111
112pub(crate) fn activate_runtime_slot(thread_id: ThreadId) -> Result<(), ObsError> {
113 let (lock, _) = &*OBS_RUNTIME_STATE;
114 let mut state = lock.lock().map_err(|_| ObsError::MutexFailure)?;
115 if *state != ObsRuntimeState::Starting {
116 return Err(ObsError::ThreadFailure);
117 }
118 *state = ObsRuntimeState::Active(thread_id);
119 Ok(())
120}
121
122pub(crate) fn begin_runtime_shutdown() {
123 let (lock, _) = &*OBS_RUNTIME_STATE;
124 match lock.lock() {
125 Ok(mut state) => {
126 if matches!(*state, ObsRuntimeState::Active(_)) {
127 *state = ObsRuntimeState::ShuttingDown;
128 }
129 }
130 Err(poisoned) => {
131 let mut state = poisoned.into_inner();
132 *state = ObsRuntimeState::ShuttingDown;
133 }
134 }
135}
136
137pub(crate) fn release_runtime_slot() -> Result<(), ObsError> {
138 let (lock, changed) = &*OBS_RUNTIME_STATE;
139 let mut state = lock.lock().map_err(|_| ObsError::MutexFailure)?;
140 *state = ObsRuntimeState::Idle;
141 changed.notify_all();
142 Ok(())
143}
144
145#[derive(Debug, Getters, Clone)]
151#[skip_new]
152pub struct ObsContext {
153 startup_info: Arc<RwLock<StartupInfo>>,
155
156 #[skip_getter]
157 objects: Arc<ObjectRegistry>,
158
159 #[skip_getter]
160 _obs_modules: Arc<ObsModules>,
161
162 runtime: ObsRuntime,
163
164 #[cfg(target_os = "linux")]
165 glib_loop: Arc<RwLock<Option<crate::utils::linux::LinuxGlibLoop>>>,
166}
167
168impl ObsContext {
169 pub fn check_version_compatibility() -> bool {
172 unsafe {
174 #[allow(unknown_lints)]
175 #[allow(ensure_obs_call_in_runtime)]
176 let version = libobs::obs_get_version_string();
177 if version.is_null() {
178 return false;
179 }
180
181 let version_str = match CStr::from_ptr(version).to_str() {
182 Ok(s) => s,
183 Err(_) => return false,
184 };
185
186 let version_parts: Vec<&str> = version_str.split('.').collect();
187 if version_parts.len() != 3 {
188 return false;
189 }
190
191 let major = match version_parts[0].parse::<u64>() {
192 Ok(v) => v,
193 Err(_) => return false,
194 };
195
196 major == libobs::LIBOBS_API_MAJOR_VER as u64
197 }
198 }
199
200 pub fn builder() -> StartupInfo {
201 StartupInfo::new()
202 }
203
204 pub fn new(info: StartupInfo) -> Result<ObsContext, ObsError> {
222 log::trace!("Getting version number...");
223
224 #[allow(unknown_lints)]
225 #[allow(ensure_obs_call_in_runtime)]
226 let version_numb = unsafe { libobs::obs_get_version() };
229 if version_numb == 0 {
230 return Err(ObsError::InvalidDll);
231 }
232
233 let (runtime, obs_modules, info) = ObsRuntime::startup(info)?;
235 #[cfg(target_os = "linux")]
236 let linux_opt = if info.start_glib_loop {
237 Some(crate::utils::linux::LinuxGlibLoop::new())
238 } else {
239 None
240 };
241
242 Ok(Self {
243 _obs_modules: Arc::new(obs_modules),
244 objects: Arc::new(ObjectRegistry::default()),
245 runtime: runtime.clone(),
246 startup_info: Arc::new(RwLock::new(info)),
247 #[cfg(target_os = "linux")]
248 glib_loop: Arc::new(RwLock::new(linux_opt)),
249 })
250 }
251
252 #[cfg(target_os = "linux")]
253 pub fn get_platform(&self) -> Result<PlatformType, ObsError> {
254 self.runtime.get_platform()
255 }
256
257 pub fn get_version(&self) -> Result<String, ObsError> {
258 Self::get_version_global()
259 }
260
261 pub fn get_version_global() -> Result<String, ObsError> {
262 unsafe {
263 #[allow(unknown_lints)]
264 #[allow(ensure_obs_call_in_runtime)]
265 let version = libobs::obs_get_version_string();
267 if version.is_null() {
268 return Err(ObsError::NullPointer(Some(
269 "OBS version string".to_string(),
270 )));
271 }
272 let version_cstr = CStr::from_ptr(version);
273 Ok(version_cstr.to_string_lossy().into_owned())
274 }
275 }
276
277 pub fn log(&self, level: ObsLogLevel, msg: &str) {
278 let mut log = LOGGER
279 .lock()
280 .unwrap_or_else(|poisoned| poisoned.into_inner());
281 log.log(level, msg.to_string());
282 }
283
284 pub fn reset_video(&mut self, ovi: ObsVideoInfo) -> Result<(), ObsError> {
299 if self
302 .startup_info
303 .read()
304 .map_err(|_| {
305 ObsError::LockError("Failed to acquire read lock on startup info".to_string())
306 })?
307 .obs_video_info
308 .graphics_module()
309 != ovi.graphics_module()
310 {
311 return Err(ObsError::ResetVideoFailureGraphicsModule);
312 }
313
314 if self.objects.any_output_active()? {
315 return Err(ObsError::ResetVideoFailureOutputActive);
316 }
317
318 let vid_ptr = Sendable(ovi.as_ptr());
325 let reset_video_status = run_with_obs!(self.runtime, (vid_ptr), move || unsafe {
326 libobs::obs_reset_video(vid_ptr.0)
328 })?;
329
330 let reset_video_status = num_traits::FromPrimitive::from_i32(reset_video_status);
331
332 let reset_video_status = match reset_video_status {
333 Some(x) => x,
334 None => ObsResetVideoStatus::Failure,
335 };
336
337 if reset_video_status == ObsResetVideoStatus::Success {
338 self.startup_info
339 .write()
340 .map_err(|_| {
341 ObsError::LockError("Failed to acquire write lock on startup info".to_string())
342 })?
343 .obs_video_info = ovi;
344
345 Ok(())
346 } else {
347 Err(ObsError::ResetVideoFailure(reset_video_status))
348 }
349 }
350
351 pub unsafe fn get_video_ptr(&self) -> Result<*mut video_output, ObsError> {
356 run_with_obs!(self.runtime, || unsafe {
358 Sendable(libobs::obs_get_video())
360 })
361 .map(|ptr| ptr.0)
362 }
363
364 pub unsafe fn get_audio_ptr(&self) -> Result<*mut audio_output, ObsError> {
369 run_with_obs!(self.runtime, || unsafe {
371 Sendable(libobs::obs_get_audio())
373 })
374 .map(|ptr| ptr.0)
375 }
376
377 pub fn data(&self) -> Result<ObsData, ObsError> {
378 ObsData::new(self.runtime.clone())
379 }
380
381 pub fn replay_buffer(
382 &mut self,
383 info: OutputInfo,
384 ) -> Result<ObsReplayBufferOutputRef, ObsError> {
385 let output = ObsReplayBufferOutputRef::new(info, self.runtime.clone());
386
387 match output {
388 Ok(x) => {
389 let tmp = x.clone();
390 self.objects.add_output(x)?;
391 Ok(tmp)
392 }
393
394 Err(x) => Err(x),
395 }
396 }
397
398 pub fn output(&mut self, info: OutputInfo) -> Result<ObsOutputRef, ObsError> {
399 let output = ObsOutputRef::new(info, self.runtime.clone());
400
401 match output {
402 Ok(x) => {
403 let tmp = x.clone();
404 self.objects.add_output(x)?;
405 Ok(tmp)
406 }
407
408 Err(x) => Err(x),
409 }
410 }
411
412 pub fn obs_filter(&mut self, info: FilterInfo) -> Result<ObsFilterRef, ObsError> {
413 let filter = ObsFilterRef::new(
414 info.id,
415 info.name,
416 info.settings,
417 info.hotkey_data,
418 self.runtime.clone(),
419 );
420
421 match filter {
422 Ok(x) => {
423 let tmp = x.clone();
424 self.objects.add_filter(x)?;
425 Ok(tmp)
426 }
427
428 Err(x) => Err(x),
429 }
430 }
431
432 #[cfg(not(target_os = "linux"))]
440 pub fn display(&mut self, data: ObsDisplayCreationData) -> Result<ObsDisplayRef, ObsError> {
441 self.inner_display_fn(data)
442 }
443
444 #[cfg(target_os = "linux")]
456 pub unsafe fn display(
457 &mut self,
458 data: ObsDisplayCreationData,
459 ) -> Result<ObsDisplayRef, ObsError> {
460 self.inner_display_fn(data)
461 }
462
463 fn inner_display_fn(
465 &mut self,
466 data: ObsDisplayCreationData,
467 ) -> Result<ObsDisplayRef, ObsError> {
468 #[cfg(target_os = "linux")]
469 {
470 let nix_display = self
473 .startup_info
474 .read()
475 .map_err(|_| {
476 ObsError::LockError("Failed to acquire read lock on startup info".to_string())
477 })?
478 .nix_display
479 .clone();
480
481 let is_wayland_handle = data.window_handle.is_wayland;
482 if is_wayland_handle && nix_display.is_none() {
483 return Err(ObsError::DisplayCreationError(
484 "Wayland window handle provided but no NixDisplay was set in StartupInfo."
485 .to_string(),
486 ));
487 }
488
489 if let Some(nix_display) = &nix_display {
490 if is_wayland_handle {
491 match nix_display {
492 crate::utils::NixDisplay::X11(_display) => {
493 return Err(ObsError::DisplayCreationError(
494 "Provided NixDisplay is X11, but the window handle is Wayland."
495 .to_string(),
496 ));
497 }
498 crate::utils::NixDisplay::Wayland(display) => {
499 use crate::utils::linux::wl_proxy_get_display;
500 if !data.window_handle.is_wayland {
501 return Err(ObsError::DisplayCreationError(
502 "Provided window handle is not a Wayland handle, but the NixDisplay is Wayland.".to_string(),
503 ));
504 }
505
506 let surface_handle = data.window_handle.window.0.display;
507 let display_from_surface = unsafe {
508 wl_proxy_get_display(surface_handle)
510 };
511 if let Err(e) = display_from_surface {
512 log::warn!("Could not get display from surface handle on wayland. Make sure your wayland client is at least version 1.23. Error: {:?}", e);
513 } else if let Ok(display_from_surface) = display_from_surface {
514 if display_from_surface != display.as_ptr() {
515 return Err(ObsError::DisplayCreationError(
516 "Provided surface handle's Wayland display does not match the NixDisplay's Wayland display.".to_string(),
517 ));
518 }
519 }
520 }
521 }
522 }
523 }
524 }
525
526 let display = ObsDisplayRef::new(data, self.runtime.clone())
527 .map_err(|e| ObsError::DisplayCreationError(e.to_string()))?;
528
529 self.objects.add_display(display.clone())?;
530 Ok(display)
531 }
532
533 pub fn remove_display(&mut self, display: &ObsDisplayRef) -> Result<(), ObsError> {
534 self.remove_display_by_id(display.id())
535 }
536
537 pub fn remove_display_by_id(&mut self, id: usize) -> Result<(), ObsError> {
538 self.objects.remove_display(id)
539 }
540
541 pub fn get_display_by_id(&self, id: usize) -> Result<Option<ObsDisplayRef>, ObsError> {
542 self.objects.display(id)
543 }
544
545 pub fn get_output(&self, name: &str) -> Result<Option<Arc<dyn ObsOutputTrait>>, ObsError> {
546 self.objects.output(name)
547 }
548
549 pub fn update_output(&self, name: &str, settings: ObsData) -> Result<(), ObsError> {
550 self.objects
551 .output(name)?
552 .ok_or(ObsError::OutputNotFound)?
553 .update_settings(settings)
554 }
555
556 pub fn get_filter(&self, name: &str) -> Result<Option<ObsFilterRef>, ObsError> {
557 self.objects.filter(name)
558 }
559
560 pub fn scene<T: Into<ObsString> + Send + Sync>(
575 &mut self,
576 name: T,
577 channel: Option<u32>,
578 ) -> Result<ObsSceneRef, ObsError> {
579 let scene = ObsSceneRef::new(name.into(), self.runtime.clone())?;
580
581 let tmp = scene.clone();
582 self.objects.add_scene(scene)?;
583
584 if let Some(channel) = channel {
585 tmp.set_to_channel(channel)?;
586 }
587 Ok(tmp)
588 }
589
590 pub fn get_scene(&self, name: &str) -> Result<Option<ObsSceneRef>, ObsError> {
591 self.objects.scene(name)
592 }
593
594 pub fn source_builder<T: ObsSourceBuilder, K: Into<ObsString> + Send + Sync>(
595 &self,
596 name: K,
597 ) -> Result<T, ObsError> {
598 T::new(name.into(), self.runtime.clone())
599 }
600}