libobs_wrapper\signals/
mod.rs

1//! Signals can be emitted by sources attached to a scene. You may implement your own signal manager
2//! by using the `impl_signal_manager` macro, but you'll need to make sure that you know which signals are emitted and what structure they have.
3mod handler;
4mod traits;
5
6pub use traits::*;
7
8/// Generates a signal manager for OBS objects that can emit signals.
9///
10/// This macro creates a complete signal management system including:
11/// - Signal handler functions that interface with OBS's C API
12/// - A manager struct that maintains signal subscriptions
13/// - Methods to subscribe to signals via `tokio::sync::broadcast` channels
14/// - Automatic cleanup on drop
15///
16/// # Parameters
17///
18/// * `$handler_getter` - A closure that takes a `SmartPointerSendable<$ptr>` and returns the raw signal handler pointer.
19///   The closure should have an explicit type annotation for the parameter and typically contains an unsafe block.
20///   Example: `|scene_ptr: SmartPointerSendable<*mut obs_scene_t>| unsafe { libobs::obs_scene_get_signal_handler(scene_ptr.get_ptr()) }`
21///
22/// * `$name` - The identifier for the generated signal manager struct.
23///
24/// * `$ptr` - The raw pointer type for the OBS object (e.g., `*mut obs_scene_t`).
25///
26/// * Signal definitions - A list of signal definitions with the following syntax:
27///   - `"signal_name": {}` - For signals with no data
28///   - `"signal_name": { field: Type }` - For signals with a single field
29///   - `"signal_name": { struct StructName { field1: Type1, field2: Type2 } }` - For signals with multiple fields
30///   - `"signal_name": { struct StructName { field1: Type1; POINTERS { ptr_field: *mut Type } } }` - For signals with both regular and pointer fields
31///
32/// # Generated Code
33///
34/// The macro generates:
35/// - A `$name` struct that manages all signal subscriptions for a single object instance
36/// - `on_<signal_name>()` methods that return `broadcast::Receiver` for each signal
37/// - Automatic signal handler registration and cleanup
38/// - Thread-safe signal dispatching using `tokio::sync::broadcast`
39///
40/// # Signal Data Types
41///
42/// Signals can carry different types of data:
43/// - **Empty signals**: Use `"signal_name": {}`
44/// - **Single value**: Use `"signal_name": { value: Type }` where Type can be primitives, String, or enums
45/// - **Struct**: Use `"signal_name": { struct Name { field1: Type1, field2: Type2 } }`
46/// - **Pointers**: Use the `POINTERS` section to mark fields as raw pointers that need special handling
47///
48/// # Safety
49///
50/// The generated code is safe to use, but relies on:
51/// - The OBS runtime being properly initialized
52/// - Smart pointers remaining valid for the lifetime of the signal manager
53/// - Signal handlers being called on the OBS thread
54///
55/// # Examples
56///
57/// ```ignore
58/// impl_signal_manager!(
59///     |scene_ptr: SmartPointerSendable<*mut obs_scene_t>| unsafe {
60///         let source_ptr = libobs::obs_scene_get_source(scene_ptr.get_ptr());
61///         libobs::obs_source_get_signal_handler(source_ptr)
62///     },
63///     ObsSceneSignals for *mut obs_scene_t,
64///     [
65///         // Simple signal with no data
66///         "refresh": {},
67///         
68///         // Signal with a single pointer field
69///         "item_add": {
70///             struct ItemAddSignal {
71///                 POINTERS {
72///                     item: *mut libobs::obs_sceneitem_t,
73///                 }
74///             }
75///         },
76///         
77///         // Signal with both regular and pointer fields
78///         "item_visible": {
79///             struct ItemVisibleSignal {
80///                 visible: bool;
81///                 POINTERS {
82///                     item: *mut libobs::obs_sceneitem_t,
83///                 }
84///             }
85///         }
86///     ]
87/// );
88/// ```
89///
90/// # Usage
91///
92/// The generated signal manager is typically stored in an `Arc` within your main struct:
93///
94/// ```ignore
95/// pub struct ObsSceneRef {
96///     signals: Arc<ObsSceneSignals>,
97///     // ... other fields
98/// }
99///
100/// impl ObsSceneRef {
101///     pub fn signals(&self) -> Arc<ObsSceneSignals> {
102///         self.signals.clone()
103///     }
104/// }
105///
106/// // Subscribe to signals
107/// let scene = ObsSceneRef::new(name, runtime)?;
108/// let signals = scene.signals();
109/// let mut rx = signals.on_refresh()?;
110///
111/// tokio::spawn(async move {
112///     while let Ok(_) = rx.recv().await {
113///         println!("Scene refreshed!");
114///     }
115/// });
116/// ```
117#[macro_export]
118macro_rules! impl_signal_manager {
119    ($handler_getter: expr, $name: ident for $ptr: ty, [
120        $($(#[$attr:meta])* $signal_name: literal: { $($inner_def:tt)* }),* $(,)*
121    ]) => {
122        paste::paste! {
123            $($crate::__signals_impl_signal!($ptr, $signal_name, $($inner_def)*);)*
124
125            $(
126            extern "C" fn [< $signal_name:snake _handler>](obj_ptr_key: *mut std::ffi::c_void, __internal_calldata: *mut libobs::calldata_t) {
127                let obj_ptr_key = obj_ptr_key as usize;
128
129                #[allow(unused_unsafe)]
130                let res = unsafe {
131                    // Safety: We are in the runtime and the calldata pointer is valid because OBS is calling this function
132                    [< $signal_name:snake _handler_inner>](__internal_calldata)
133                };
134                if res.is_err() {
135                    log::warn!("Error processing signal {}: {:?}", stringify!($signal_name), res.err());
136                    return;
137                }
138
139                let res = res.unwrap();
140                let senders = [<$signal_name:snake:upper _SENDERS>].read();
141                if let Err(e) = senders {
142                    log::warn!("Failed to acquire read lock for signal {}: {}", stringify!($signal_name), e);
143                    return;
144                }
145
146                let senders = senders.unwrap();
147                let senders = senders.get(&obj_ptr_key);
148                if senders.is_none() {
149                    log::warn!("No sender found for signal {}", stringify!($signal_name));
150                    return;
151                }
152
153                let senders = senders.unwrap();
154                let _ = senders.send(res);
155            })*
156
157            /// This signal manager must be within an `Arc` if you want to clone it.
158            #[derive(Debug)]
159            pub struct $name {
160                runtime: $crate::runtime::ObsRuntime,
161                pointer: $crate::unsafe_send::SmartPointerSendable<$ptr>,
162            }
163
164            impl $name {
165                fn smart_ptr_to_key(ptr: &$crate::unsafe_send::SmartPointerSendable<$ptr>) -> usize {
166                    ptr.get_ptr() as usize
167                }
168
169                pub(crate) fn new(smart_ptr: &$crate::unsafe_send::SmartPointerSendable<$ptr>, runtime: $crate::runtime::ObsRuntime) -> Result<Self, $crate::utils::ObsError> {
170                    use $crate::utils::ObsString;
171                    let smart_ptr = smart_ptr.clone();
172                    let smart_ptr_as_key = Self::smart_ptr_to_key(&smart_ptr);
173
174                    $(
175                        let senders = [<$signal_name:snake:upper _SENDERS>].clone();
176                        let senders = senders.write();
177                        if senders.is_err() {
178                            return Err($crate::utils::ObsError::LockError("Failed to acquire write lock for signal senders".to_string()));
179                        }
180
181                        let (tx, [<_ $signal_name:snake _rx>]) = tokio::sync::broadcast::channel(16);
182                        let mut senders = senders.unwrap();
183                        // Its fine since we are just using the pointer as key
184                        senders.insert(smart_ptr_as_key.clone(), tx);
185                    )*
186
187                    $crate::run_with_obs!(runtime, (smart_ptr_as_key, smart_ptr), move || {
188                            let handler = ($handler_getter)(smart_ptr);
189                            $(
190                                let signal = ObsString::new($signal_name);
191                                unsafe {
192                                    // Safety: We know that the handler must exist, the signal is still in scope, so the ptr to that is valid as well and we are just using the raw_ptr as key in the handler function.
193                                    libobs::signal_handler_connect(
194                                        handler,
195                                        signal.as_ptr().0,
196                                        Some([< $signal_name:snake _handler>]),
197                                        // We are just casting it back to a usize in the handler function
198                                        smart_ptr_as_key as *mut std::ffi::c_void,
199                                    );
200                                };
201                            )*
202                    })?;
203
204                    Ok(Self {
205                        pointer: smart_ptr,
206                        runtime
207                    })
208                }
209
210                $(
211                    $(#[$attr])*
212                    pub fn [<on_ $signal_name:snake>](&self) -> Result<tokio::sync::broadcast::Receiver<[<__Private $signal_name:camel Type >]>, $crate::utils::ObsError> {
213                        let handlers = [<$signal_name:snake:upper _SENDERS>].read();
214                        if handlers.is_err() {
215                            return Err($crate::utils::ObsError::LockError("Failed to acquire read lock for signal senders".to_string()));
216                        }
217
218                        let handlers = handlers.unwrap();
219                        let handler_key = Self::smart_ptr_to_key(&self.pointer);
220                        let rx = handlers.get(&handler_key)
221                            .ok_or_else(|| $crate::utils::ObsError::NoSenderError)?
222                            .subscribe();
223
224                        Ok(rx)
225                    }
226                )*
227            }
228
229            impl Drop for $name {
230                fn drop(&mut self) {
231                    log::trace!("Dropping signal manager {}...", stringify!($name));
232
233                    #[allow(unused_variables)]
234                    let ptr = self.pointer.clone();
235                    #[allow(unused_variables)]
236                    let runtime = self.runtime.clone();
237
238                    //TODO make this non blocking
239                    let future = $crate::run_with_obs!(runtime, (ptr), move || {
240                        #[allow(unused_variables)]
241                        let handler = ($handler_getter)(ptr.clone());
242                        $(
243                            let signal = $crate::utils::ObsString::new($signal_name);
244                            unsafe {
245                                // Safety: We are in the runtime, the signal string is allocated, we still have the drop guard as ptr in this scope so the handler is valid.
246                                libobs::signal_handler_disconnect(
247                                    handler,
248                                    signal.as_ptr().0,
249                                    Some([< $signal_name:snake _handler>]),
250                                    ptr.get_ptr() as *mut std::ffi::c_void,
251                                );
252                            }
253                        )*
254                    });
255
256                    let r = {
257                        $(
258                            let handlers = [<$signal_name:snake:upper _SENDERS>].write();
259                            if handlers.is_err() {
260                                log::warn!("Failed to acquire write lock for signal {} senders during drop", stringify!($signal_name));
261                                return;
262                            }
263
264                            let mut handlers = handlers.unwrap();
265                            handlers.remove(&Self::smart_ptr_to_key(&self.pointer));
266                        )*
267
268                        future
269                    };
270
271                    if std::thread::panicking() {
272                        return;
273                    }
274
275                    r.unwrap();
276                }
277            }
278        }
279    };
280}