libobs_wrapper\utils/
calldata.rs

1use std::{
2    ffi::{c_char, CStr},
3    mem::MaybeUninit,
4    pin::Pin,
5    sync::Arc,
6};
7
8use libobs::{calldata_t, proc_handler_t};
9
10use crate::{
11    context::ObsContext,
12    impl_obs_drop, run_with_obs,
13    runtime::ObsRuntime,
14    unsafe_send::Sendable,
15    utils::{calldata_free, ObsError, ObsString},
16};
17
18/// RAII wrapper for libobs `calldata_t` ensuring stable address and proper free.
19pub struct CalldataWrapper {
20    // We need to make sure the data is freed in OBS BEFORE it is dropped
21    _drop_guard: Arc<_CalldataWrapperDropGuard>,
22    // Not using a SmartPointerSendable here because its just way too complicated if we want to get the inner mut pointer
23    //TODO ?
24    data: Sendable<Pin<Box<calldata_t>>>, // stable address
25    runtime: ObsRuntime,
26}
27
28impl CalldataWrapper {
29    /// Returns a mutable pointer to the inner `calldata_t`.
30    /// # Safety
31    ///
32    /// This function is unsafe. You must guarantee that you will never move
33    /// the data out of the mutable reference you receive when you call this
34    /// function, so that the invariants on the `Pin` type can be upheld.
35    pub unsafe fn as_mut_ptr(&mut self) -> Sendable<*mut calldata_t> {
36        // Safe: pinned box guarantees stable location; we only get a mutable reference.
37        let r: &mut calldata_t = Pin::as_mut(&mut self.data.0).get_unchecked_mut();
38        Sendable(r as *mut _)
39    }
40
41    /// Extracts a C string pointer for the given key from the calldata.
42    pub fn get_string<T: Into<ObsString>>(&mut self, key: T) -> Result<String, ObsError> {
43        let key: ObsString = key.into();
44        let self_ptr = unsafe {
45            // Safety: We won't modify the calldata, so it's safe to get a mutable pointer here.
46            self.as_mut_ptr()
47        };
48
49        let _drop_guard = self._drop_guard.clone(); // Ensure runtime is valid during the call
50        let value = run_with_obs!(
51            self.runtime.clone(),
52            (_drop_guard, self_ptr, key),
53            move || {
54                let key_ptr = key.as_ptr().0;
55
56                let mut data = MaybeUninit::<*const c_char>::uninit();
57                let ok = unsafe {
58                    // Safety: self_ptr and key_ptr are valid pointers.
59                    libobs::calldata_get_string(self_ptr.0, key_ptr, data.as_mut_ptr())
60                };
61                if !ok {
62                    return Err(ObsError::Unexpected(format!(
63                        "Calldata String {key} not found."
64                    )));
65                }
66
67                let data_ptr = unsafe {
68                    // Safety: data was initialized by calldata_get_string, and we made sure the call was ok.
69                    data.assume_init()
70                };
71                if data_ptr.is_null() {
72                    return Err(ObsError::Unexpected(format!(
73                        "Calldata String {key} is null."
74                    )));
75                }
76
77                let data = unsafe {
78                    // Safety: data_ptr is a valid C string pointer because it is not null.
79                    CStr::from_ptr(data_ptr)
80                };
81                let data = data.to_str();
82                if let Err(_e) = data {
83                    return Err(ObsError::Unexpected(format!(
84                        "Calldata String {key} is not valid UTF-8."
85                    )));
86                }
87
88                let data = data.unwrap();
89                Ok(data.to_string())
90            }
91        )??;
92
93        Ok(value)
94    }
95
96    //TODO implement calldata get_data type but I think this is hard to safely do this
97}
98
99struct _CalldataWrapperDropGuard {
100    calldata_ptr: Sendable<*mut calldata_t>,
101    runtime: ObsRuntime,
102}
103
104impl_obs_drop!(_CalldataWrapperDropGuard, (calldata_ptr), move || unsafe {
105    // Safety: We are in the runtime and drop guards are always constructed frm valid calldata pointers.
106    calldata_free(calldata_ptr.0);
107});
108
109/// Extension trait on `ObsRuntime` to call a proc handler and return a RAII calldata wrapper.
110pub trait ObsCalldataExt {
111    /// # Safety
112    /// Make sure that the proc_handler pointer is valid.
113    unsafe fn call_proc_handler<T: Into<ObsString>>(
114        &self,
115        proc_handler: &Sendable<*mut proc_handler_t>,
116        name: T,
117    ) -> Result<CalldataWrapper, ObsError>;
118}
119
120impl ObsCalldataExt for ObsRuntime {
121    unsafe fn call_proc_handler<T: Into<ObsString>>(
122        &self,
123        proc_handler: &Sendable<*mut proc_handler_t>,
124        name: T,
125    ) -> Result<CalldataWrapper, ObsError> {
126        if proc_handler.0.is_null() {
127            return Err(ObsError::NullPointer(None));
128        }
129
130        let proc_handler = proc_handler.clone();
131        let name: ObsString = name.into();
132        let mut calldata = run_with_obs!(self.clone(), (proc_handler, name), move || {
133            // Safety: calldata will be properly freed by the drop guard, and we are using a struct for the `zeroed` call.
134            let data: calldata_t = unsafe { std::mem::zeroed() };
135            let mut data = Box::pin(data);
136            // Safety: Data will not get moved out of the pinned box, only the proc handler call will use the pointer and not move it.
137            let raw_ptr = unsafe { Pin::as_mut(&mut data).get_unchecked_mut() };
138
139            // Safety: the caller must have made sure that the proc handler is valid, the name pointer and the raw_ptr of the calldata is valid.
140            let ok = unsafe { libobs::proc_handler_call(proc_handler.0, name.as_ptr().0, raw_ptr) };
141            if !ok {
142                return Err(ObsError::Unexpected(
143                    "Couldn't call proc handler".to_string(),
144                ));
145            }
146
147            Ok(Sendable(data))
148        })??;
149
150        // Safety: Data will never get moved out of the pinned box, as this pointer will only be used on drop and then freed.
151        let calldata_ptr = unsafe { Pin::as_mut(&mut calldata.0).get_unchecked_mut() };
152
153        // Pin the calldata to a stable heap location and create a drop guard.
154        let guard = Arc::new(_CalldataWrapperDropGuard {
155            calldata_ptr: Sendable(calldata_ptr),
156            runtime: self.clone(),
157        });
158
159        Ok(CalldataWrapper {
160            data: calldata,
161            runtime: self.clone(),
162            _drop_guard: guard,
163        })
164    }
165}
166
167impl ObsCalldataExt for ObsContext {
168    unsafe fn call_proc_handler<T: Into<ObsString>>(
169        &self,
170        proc_handler: &Sendable<*mut proc_handler_t>,
171        name: T,
172    ) -> Result<CalldataWrapper, ObsError> {
173        self.runtime().call_proc_handler(proc_handler, name)
174    }
175}