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