Skip to main content

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