libobs_simple_macro/
lib.rs

1use obs_properties::obs_properties_to_functions;
2use parse::UpdaterInput;
3use proc_macro::TokenStream;
4use quote::{format_ident, quote};
5use syn::{parse_macro_input, Data, DeriveInput, Fields, ItemImpl, LitStr, Type, TypePath};
6
7mod docs;
8mod fields;
9mod obs_properties;
10mod parse;
11
12/// Generates an updater struct for an OBS object (e.g., a source).
13///
14/// This macro creates a struct that implements `ObsObjectUpdater`, allowing you to modify
15/// the settings of an existing OBS object at runtime.
16///
17/// # Arguments
18///
19/// * `name` - The unique ID of the OBS object (must match the ID used in `obs_object_builder`).
20/// * `updatable_type` - The type of the struct that holds the object's state.
21///
22/// # Example
23///
24/// ```ignore
25/// #[obs_object_updater("my_source", ObsSourceRef)]
26/// pub struct MySourceUpdater {
27///     #[obs_property(type_t = "string")]
28///     pub url: String,
29/// }
30/// ```
31#[proc_macro_attribute]
32pub fn obs_object_updater(attr: TokenStream, item: TokenStream) -> TokenStream {
33    let u_input = parse_macro_input!(attr as UpdaterInput);
34    let id_value = u_input.name.value();
35    let updatable_type = u_input.updatable_type;
36
37    let input = parse_macro_input!(item as DeriveInput);
38
39    let i_ident = input.ident;
40    let updater_name = format_ident!("{}", i_ident);
41
42    let visibility = input.vis;
43    let attributes = input.attrs;
44
45    let fields = match input.data {
46        Data::Struct(data) => match data.fields {
47            Fields::Named(fields) => fields.named,
48            _ => panic!("Only named fields are supported"),
49        },
50        _ => panic!("Only structs are supported"),
51    };
52
53    let (struct_fields, struct_initializers) = fields::generate_struct_fields(&fields);
54    let functions = obs_properties_to_functions(
55        &fields,
56        quote! {
57            use libobs_wrapper::data::ObsObjectUpdater;
58            self.get_settings_updater()
59        },
60    );
61
62    let updatable_type2 = updatable_type.clone();
63    let expanded = quote! {
64        #(#attributes)*
65        #[allow(dead_code)]
66        #visibility struct #updater_name<'a> {
67            #(#struct_fields,)*
68            settings: libobs_wrapper::data::ObsData,
69            settings_updater: libobs_wrapper::data::ObsDataUpdater,
70            updatable: &'a mut #updatable_type2
71        }
72
73        impl <'a> libobs_wrapper::data::ObsObjectUpdater<'a> for #updater_name<'a> {
74            type ToUpdate = #updatable_type;
75
76            fn create_update(runtime: libobs_wrapper::runtime::ObsRuntime, updatable: &'a mut Self::ToUpdate) -> Result<Self, libobs_wrapper::utils::ObsError> {
77                let source_id = Self::get_id();
78                let flags = unsafe {
79                    libobs::obs_get_source_output_flags(source_id.as_ptr().0)
80                };
81
82                if flags == 0 {
83                    return Err(libobs_wrapper::utils::ObsError::SourceNotAvailable(source_id.to_string()))
84                }
85
86                let mut settings = libobs_wrapper::data::ObsData::new(runtime.clone())?;
87
88                Ok(Self {
89                    #(#struct_initializers,)*
90                    settings_updater: settings.bulk_update(),
91                    settings,
92                    updatable,
93                })
94            }
95
96            fn get_settings(&self) -> &libobs_wrapper::data::ObsData {
97                &self.settings
98            }
99
100            fn get_settings_updater(&mut self) -> &mut libobs_wrapper::data::ObsDataUpdater {
101                &mut self.settings_updater
102            }
103
104            fn get_id() -> libobs_wrapper::utils::ObsString {
105                #id_value.into()
106            }
107
108            fn update(self) -> Result<(), libobs_wrapper::utils::ObsError> {
109                use libobs_wrapper::utils::traits::ObsUpdatable;
110                let #updater_name {
111                    settings_updater,
112                    updatable,
113                    settings,
114                    ..
115                } = self;
116
117                log::trace!("Updating settings for {:?}", Self::get_id());
118                settings_updater.update()?;
119
120                log::trace!("Updating raw settings for {:?}", Self::get_id());
121                let e = updatable.update_raw(settings);
122                log::trace!("Update done for {:?}", Self::get_id());
123
124                e
125            }
126        }
127
128        impl <'a> #updater_name <'a> {
129            #(#functions)*
130        }
131    };
132
133    TokenStream::from(expanded)
134}
135
136#[proc_macro_attribute]
137/// Generates a builder struct for an OBS object (e.g., a source).
138///
139/// This macro creates a struct that implements `ObsObjectBuilder`, allowing you to configure
140/// and create new instances of an OBS object.
141///
142/// # Arguments
143///
144/// * `attr` - The unique ID of the OBS object (e.g., "window_capture").
145///
146/// # Fields
147///
148/// Each field in the struct must be annotated with `#[obs_property(type_t = "...")]`.
149/// Supported `type_t` values:
150///
151/// ```rust
152/// use libobs_wrapper::data::StringEnum;
153/// use libobs_simple_macro::obs_object_builder;
154/// use num_derive::{FromPrimitive, ToPrimitive};
155///
156/// #[repr(i32)]
157/// #[derive(Clone, Copy, Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)]
158/// pub enum ObsWindowCaptureMethod {
159///        MethodAuto = libobs::window_capture_method_METHOD_AUTO,
160///        MethodBitBlt = libobs::window_capture_method_METHOD_BITBLT,
161///        MethodWgc = libobs::window_capture_method_METHOD_WGC,
162/// }
163///
164/// #[derive(Clone, Copy, Debug, PartialEq, Eq)]
165/// pub enum ObsGameCaptureRgbaSpace {
166///     SRgb,
167///     RGBA2100pq
168/// }
169///
170/// impl StringEnum for ObsGameCaptureRgbaSpace {
171///     fn to_str(&self) -> &str {
172///         match self {
173///             ObsGameCaptureRgbaSpace::SRgb => "sRGB",
174///             ObsGameCaptureRgbaSpace::RGBA2100pq => "Rec. 2100 (PQ)"
175///         }
176///     }
177/// }
178///
179/// /// Provides an easy-to-use builder for the window capture source.
180/// #[derive(Debug)]
181/// #[obs_object_builder("window_capture")]
182/// pub struct WindowCaptureSourceBuilder {
183/// #[obs_property(type_t="enum")]
184///     /// Sets the capture method for the window capture
185///     capture_method: ObsWindowCaptureMethod,
186///
187/// #[obs_object_builder("my_source")]
188/// pub struct MySourceBuilder {
189///     #[obs_property(type_t = "string")]
190///     pub url: String,
191/// }
192/// ```
193pub fn obs_object_builder(attr: TokenStream, item: TokenStream) -> TokenStream {
194    let id = parse_macro_input!(attr as LitStr);
195
196    let input = parse_macro_input!(item as DeriveInput);
197
198    let i_ident = input.ident;
199    let builder_name = format_ident!("{}", i_ident);
200
201    let generics = input.generics;
202    let visibility = input.vis;
203    let attributes = input.attrs;
204
205    let fields = match input.data {
206        Data::Struct(data) => match data.fields {
207            Fields::Named(fields) => fields.named,
208            _ => panic!("Only named fields are supported"),
209        },
210        _ => panic!("Only structs are supported"),
211    };
212
213    let id_value = id.value();
214    let (struct_fields, struct_initializers) = fields::generate_struct_fields(&fields);
215
216    let functions = obs_properties_to_functions(
217        &fields,
218        quote! {
219            use libobs_wrapper::data::ObsObjectBuilder;
220            self.get_settings_updater()
221        },
222    );
223
224    let expanded = quote! {
225        #(#attributes)*
226        #[allow(dead_code)]
227        #visibility struct #builder_name #generics {
228            #(#struct_fields,)*
229            settings: libobs_wrapper::data::ObsData,
230            settings_updater: libobs_wrapper::data::ObsDataUpdater,
231            hotkeys: libobs_wrapper::data::ObsData,
232            hotkeys_updater: libobs_wrapper::data::ObsDataUpdater,
233            name: libobs_wrapper::utils::ObsString,
234            runtime: libobs_wrapper::runtime::ObsRuntime
235        }
236
237        impl libobs_wrapper::data::ObsObjectBuilder for #builder_name {
238            fn new<T: Into<libobs_wrapper::utils::ObsString> + Send + Sync>(name: T, runtime: libobs_wrapper::runtime::ObsRuntime) -> Result<Self, libobs_wrapper::utils::ObsError> {
239                let name = name.into();
240                let source_id = Self::get_id();
241                let flags = unsafe {
242                    libobs::obs_get_source_output_flags(source_id.as_ptr().0)
243                };
244
245                if flags == 0 {
246                    return Err(libobs_wrapper::utils::ObsError::SourceNotAvailable(source_id.to_string()))
247                }
248
249                let mut hotkeys = libobs_wrapper::data::ObsData::new(runtime.clone())?;
250                let mut settings = libobs_wrapper::data::ObsData::new(runtime.clone())?;
251
252                Ok(Self {
253                    #(#struct_initializers,)*
254                    name,
255                    settings_updater: settings.bulk_update(),
256                    settings,
257                    hotkeys_updater: hotkeys.bulk_update(),
258                    hotkeys,
259                    runtime
260                })
261            }
262
263            fn get_settings(&self) -> &libobs_wrapper::data::ObsData {
264                &self.settings
265            }
266
267            fn get_settings_updater(&mut self) -> &mut libobs_wrapper::data::ObsDataUpdater {
268                &mut self.settings_updater
269            }
270
271            fn get_hotkeys(&self) -> &libobs_wrapper::data::ObsData {
272                &self.hotkeys
273            }
274
275            fn get_hotkeys_updater(&mut self) -> &mut libobs_wrapper::data::ObsDataUpdater {
276                &mut self.hotkeys_updater
277            }
278
279            fn get_name(&self) -> libobs_wrapper::utils::ObsString {
280                self.name.clone()
281            }
282
283            fn get_id() -> libobs_wrapper::utils::ObsString {
284                #id_value.into()
285            }
286
287            fn build(self) -> Result<libobs_wrapper::utils::ObjectInfo, libobs_wrapper::utils::ObsError> {
288                let name = self.get_name();
289                let #builder_name {
290                    settings_updater,
291                    hotkeys_updater,
292                    settings,
293                    hotkeys,
294                    ..
295                } = self;
296
297                settings_updater.update()?;
298                hotkeys_updater.update()?;
299
300                Ok(libobs_wrapper::utils::ObjectInfo::new(
301                    Self::get_id(),
302                    name,
303                    Some(settings),
304                    Some(hotkeys),
305                ))
306            }
307        }
308
309        impl #builder_name {
310            #(#functions)*
311        }
312    };
313
314    TokenStream::from(expanded)
315}
316
317/// Implements the builder and updater logic for an OBS object.
318///
319/// This macro generates the implementation blocks for the builder and updater structs
320/// created by `obs_object_builder` and `obs_object_updater`. It should be applied
321/// to the implementation block of the main object struct.
322///
323/// # Example
324///
325/// ```ignore
326/// #[obs_object_impl]
327/// impl MySource {
328///     // Custom methods...
329/// }
330/// ```
331#[proc_macro_attribute]
332pub fn obs_object_impl(_attr: TokenStream, item: TokenStream) -> TokenStream {
333    let input = parse_macro_input!(item as ItemImpl);
334
335    // Extract the function from the implementation
336    let impl_item = input.items;
337    let impl_item2 = impl_item.clone();
338
339    // Create the builder and updater struct names
340    let base_name = if let Type::Path(TypePath { path, .. }) = &*input.self_ty {
341        path.segments.last().unwrap().ident.to_string()
342    } else {
343        panic!("Only path types are supported in self_ty")
344    };
345
346    let builder_name = format_ident!("{}Builder", base_name);
347    let updater_name = format_ident!("{}Updater", base_name);
348
349    let expanded = quote! {
350        // Builder implementation
351        impl #builder_name {
352            #(#impl_item)*
353        }
354
355        // Updater implementation with lifetime
356        impl<'a> #updater_name<'a> {
357            #(#impl_item2)*
358        }
359    };
360
361    TokenStream::from(expanded)
362}