ndi_sdk_sys/
source.rs

1//! Source descriptors are used to get the name of local and discovered senders as well as telling a receiver unambiguously which source to connect to
2//!
3//! A source descriptor contains the name (exposed via `.name()`) and
4//! optional internal connection info (not exposed, used to avoid creating an internal finder to resolve the name)
5
6use std::{
7    ffi::{CStr, CString},
8    fmt::Debug,
9    marker::PhantomData,
10    ptr,
11};
12
13use static_assertions::assert_impl_all;
14
15use crate::{
16    bindings,
17    util::{SourceNameError, validate_source_name},
18};
19
20/// Everything that can be used to refer to an NDI source
21///
22/// # Safety
23///
24/// the [NDISourceLike::with_descriptor] itself is safe, but unsafe code relies on this being correct, so the whole trait has to be considered unsafe.
25pub unsafe trait NDISourceLike: Debug {
26    /// This method builds a source descriptor for the NDI library that is valid for the duration of the closure call
27    ///
28    /// # Safety
29    ///
30    /// The pointer passed to the closure is valid (if non-null) for the duration of the closure call.
31    ///
32    /// This might be a null pointer if it is generated from `NullPtrSource` or `Option<NDISourceLike>::None`.
33    fn with_descriptor<R>(&self, f: impl FnOnce(*const bindings::NDIlib_source_t) -> R) -> R;
34}
35
36/// Represents no source, equivalent to passing a nullptr in the C SDK
37#[derive(Debug, Clone, Copy)]
38pub(crate) struct NullPtrSource;
39
40unsafe impl NDISourceLike for NullPtrSource {
41    /// # Safety
42    ///
43    /// This is always a null pointer, as permitted by the trait
44    fn with_descriptor<R>(&self, f: impl FnOnce(*const bindings::NDIlib_source_t) -> R) -> R {
45        f(ptr::null())
46    }
47}
48
49unsafe impl<S: NDISourceLike> NDISourceLike for Option<S> {
50    fn with_descriptor<R>(&self, f: impl FnOnce(*const bindings::NDIlib_source_t) -> R) -> R {
51        if let Some(source) = self {
52            source.with_descriptor(f)
53        } else {
54            NullPtrSource.with_descriptor(f)
55        }
56    }
57}
58
59/// This is a short-lived/reference source descriptor
60///
61/// A source descriptor contains the name (exposed via [NDISourceRef::name]) and
62/// optional internal connection info (not exposed, used to avoid creating an internal finder to resolve the name)
63///
64/// C equivalent: `NDIlib_source_t`
65#[derive(Clone)]
66pub struct NDISourceRef<'a> {
67    // this points to the name inside raw
68    name: &'a CStr,
69    raw: bindings::NDIlib_source_t,
70    raw_ptrs: PhantomData<&'a CStr>,
71}
72// needs to be evaluated
73// unsafe impl Send for NDISourceRef<'_> {}
74
75impl<'a> NDISourceRef<'a> {
76    pub(crate) unsafe fn from(source_t: bindings::NDIlib_source_t) -> Self {
77        if source_t.p_ndi_name.is_null() {
78            panic!("[Fatal FFI Error] NDI SDK returned nullptr for source name")
79        } else {
80            let name = unsafe { CStr::from_ptr(source_t.p_ndi_name) };
81            NDISourceRef {
82                name,
83                raw: source_t,
84                raw_ptrs: PhantomData,
85            }
86        }
87    }
88
89    /// Gets the name of the source
90    pub fn name(&'a self) -> &'a CStr {
91        self.name
92    }
93
94    /// Converts it to an owned [NDISource] by cloning all information
95    pub fn to_owned(&self) -> NDISource {
96        let descriptor_anon_1 = self.raw.__bindgen_anon_1;
97        let descriptor_anon_1: *const ::std::os::raw::c_char =
98            unsafe { descriptor_anon_1.p_url_address };
99        let descriptor_anon_1 = if descriptor_anon_1.is_null() {
100            None
101        } else {
102            Some(unsafe { CStr::from_ptr(descriptor_anon_1).to_owned() })
103        };
104
105        let name = self.name.to_owned();
106
107        NDISource {
108            name: name.clone().into_string().unwrap(),
109            name_c: name,
110            descriptor_anon_1,
111        }
112    }
113}
114
115unsafe impl NDISourceLike for &NDISourceRef<'_> {
116    fn with_descriptor<R>(&self, f: impl FnOnce(*const bindings::NDIlib_source_t) -> R) -> R {
117        f(&self.raw)
118    }
119}
120
121impl std::fmt::Debug for NDISourceRef<'_> {
122    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123        let descriptor_anon_1 = self.raw.__bindgen_anon_1;
124        let descriptor_anon_1: *const ::std::os::raw::c_char =
125            unsafe { descriptor_anon_1.p_url_address };
126
127        let mut dbg = f.debug_struct("NDISourceRef");
128
129        dbg.field("name", &self.name.to_str());
130
131        if !descriptor_anon_1.is_null() {
132            let descriptor = unsafe { CStr::from_ptr(descriptor_anon_1) };
133            dbg.field("raw_src", &descriptor);
134        } else {
135            dbg.field("raw_src", &"null");
136        }
137
138        dbg.finish()
139    }
140}
141
142/// long-lived/owned source descriptor
143///
144/// A source descriptor contains the name (exposed via [NDISource::name]) and
145/// optional internal connection info (not exposed, used to avoid creating an internal finder to resolve the name)
146#[derive(Clone, Hash, PartialEq, Eq)]
147pub struct NDISource {
148    name: String,
149    name_c: CString,
150    descriptor_anon_1: Option<CString>,
151}
152
153assert_impl_all!(NDISource: Send, Sync);
154
155impl NDISource {
156    pub fn from_name(name: &str) -> Result<Self, SourceNameError> {
157        let name_c = validate_source_name(name)?;
158        Ok(NDISource {
159            name: name.to_owned(),
160            name_c,
161            descriptor_anon_1: None,
162        })
163    }
164
165    pub fn name(&self) -> &str {
166        self.name.as_str()
167    }
168
169    pub fn name_c_str(&self) -> &CStr {
170        &self.name_c
171    }
172}
173
174unsafe impl NDISourceLike for NDISource {
175    fn with_descriptor<R>(&self, f: impl FnOnce(*const bindings::NDIlib_source_t) -> R) -> R {
176        let descriptor = bindings::NDIlib_source_t {
177            p_ndi_name: self.name_c.as_ptr(),
178            __bindgen_anon_1: bindings::NDIlib_source_t__bindgen_ty_1 {
179                p_url_address: self
180                    .descriptor_anon_1
181                    .as_ref()
182                    .map(|s| s.as_ptr())
183                    .unwrap_or(ptr::null()),
184            },
185        };
186        f(&descriptor)
187    }
188}
189
190unsafe impl NDISourceLike for &NDISource {
191    fn with_descriptor<R>(&self, f: impl FnOnce(*const bindings::NDIlib_source_t) -> R) -> R {
192        (*self).with_descriptor(f)
193    }
194}
195
196impl std::fmt::Debug for NDISource {
197    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
198        f.debug_struct("NDISource")
199            .field("name", &self.name)
200            .field("raw_src", &self.descriptor_anon_1)
201            .finish()
202    }
203}