1mod error;
2mod find;
3mod routing;
4mod source;
5pub mod sys;
6
7pub use error::{NDIError, NDIResult};
8pub use find::*;
9pub use routing::*;
10pub use source::*;
11
12use libloading::{Library, Symbol};
13use std::path::PathBuf;
14use std::sync::LazyLock;
15use std::{env, ffi, fs};
16
17pub(crate) static HANDLE: LazyLock<NDILib> =
18 LazyLock::new(|| initialize().expect("Expected to lazily initialize NDI library handle"));
19
20pub(crate) struct NDILib {
21 pub(crate) lib: *const sys::NDIlib_v5,
22}
23unsafe impl Send for NDILib {}
24unsafe impl Sync for NDILib {}
25
26fn initialize() -> NDIResult<NDILib> {
28 let ndi_lib = load_lib()?;
29 let load_fn: Symbol<unsafe extern "C" fn() -> *const sys::NDIlib_v5> =
30 unsafe { ndi_lib.get(b"NDIlib_v5_load\0")? };
31
32 let v5 = unsafe { load_fn() };
33 if v5.is_null() {
34 return Err(NDIError::LoadV5Failed);
35 }
36
37 let Some(init) = (unsafe { (*v5).initialize }) else {
38 return Err(NDIError::MissingSymbolV5("initialize"));
39 };
40
41 if unsafe { init() } {
42 Ok(NDILib { lib: v5 })
43 } else {
44 Err(NDIError::InitializeFailed)
45 }
46}
47
48fn load_lib() -> NDIResult<Library> {
50 let lib_name = if cfg!(target_os = "macos") {
51 "libndi.dylib"
52 } else if cfg!(unix) {
53 "libndi.so.6"
54 } else if cfg!(all(windows, target_pointer_width = "64")) {
55 "Processing.NDI.Lib.x64.dll"
56 } else if cfg!(all(windows, target_pointer_width = "32")) {
57 "Processing.NDI.Lib.x86.dll"
58 } else {
59 panic!("NDI SDK is only supported on Linux, macOS and Windows x86/x64")
60 };
61
62 let mut search_paths = Vec::new();
63 if let Ok(rtd) = env::var("NDI_RUNTIME_DIR_V6") {
64 let p = PathBuf::from(rtd);
65 if cfg!(windows) {
66 search_paths.push(p.join("lib"));
67 } else {
68 search_paths.push(p);
69 }
70 };
71
72 #[cfg(unix)]
73 search_paths.push(PathBuf::from("/usr/local/lib"));
74 #[cfg(all(unix, not(target_os = "macos")))]
75 search_paths.push(PathBuf::from("/usr/lib"));
76
77 for path in search_paths {
78 let file = path.join(lib_name);
79 if let Ok(true) = fs::exists(&file) {
80 return Ok(unsafe { Library::new(file)? });
81 }
82 }
83
84 Ok(unsafe { Library::new(lib_name)? })
86}
87
88pub fn version() -> NDIResult<String> {
89 let Some(version_fn) = (unsafe { (*HANDLE.lib).version }) else {
90 return Err(NDIError::MissingSymbolV5("version"));
91 };
92
93 let version_ptr = unsafe { version_fn() };
94 if version_ptr.is_null() {
95 return Err(NDIError::UnexpectedNullPointer("version"));
96 }
97 let c_str = unsafe { ffi::CStr::from_ptr(version_ptr) };
98 Ok(c_str.to_str().map(|s| s.to_owned())?)
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn test_version_alloc() -> NDIResult<()> {
107 unsafe {
108 let vp_1 = (*HANDLE.lib).version.unwrap()();
109 let vp_2 = (*HANDLE.lib).version.unwrap()();
110 assert_eq!(vp_1, vp_2);
112 }
113 println!("Version: {}", version()?);
114 Ok(())
115 }
116}