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, #underlying_ptr_type> 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_ptr().0)
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/// ```rust
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/// #[obs_object_builder("my_source")]
194/// pub struct MySourceBuilder {
195///     #[obs_property(type_t = "string")]
196///     pub url: String,
197/// }
198/// ```
199pub fn obs_object_builder(attr: TokenStream, item: TokenStream) -> TokenStream {
200    let id = parse_macro_input!(attr as LitStr);
201
202    let input = parse_macro_input!(item as DeriveInput);
203
204    let i_ident = input.ident;
205    let builder_name = format_ident!("{}", i_ident);
206
207    let generics = input.generics;
208    let visibility = input.vis;
209    let attributes = input.attrs;
210
211    let fields = match input.data {
212        Data::Struct(data) => match data.fields {
213            Fields::Named(fields) => fields.named,
214            _ => panic!("Only named fields are supported"),
215        },
216        _ => panic!("Only structs are supported"),
217    };
218
219    let id_value = id.value();
220    let (struct_fields, struct_initializers) = fields::generate_struct_fields(&fields);
221
222    let functions = obs_properties_to_functions(
223        &fields,
224        quote! {
225            use libobs_wrapper::data::ObsObjectBuilder;
226            self.get_settings_updater()
227        },
228    );
229
230    let expanded = quote! {
231        #(#attributes)*
232        #[allow(dead_code)]
233        #visibility struct #builder_name #generics {
234            #(#struct_fields,)*
235            settings: libobs_wrapper::data::ObsData,
236            settings_updater: libobs_wrapper::data::ObsDataUpdater,
237            hotkeys: libobs_wrapper::data::ObsData,
238            hotkeys_updater: libobs_wrapper::data::ObsDataUpdater,
239            name: libobs_wrapper::utils::ObsString,
240            runtime: libobs_wrapper::runtime::ObsRuntime
241        }
242
243        impl libobs_wrapper::data::ObsObjectBuilder for #builder_name {
244            fn new<T: Into<libobs_wrapper::utils::ObsString> + Send + Sync>(name: T, runtime: libobs_wrapper::runtime::ObsRuntime) -> Result<Self, libobs_wrapper::utils::ObsError> {
245                let name = name.into();
246                let source_id = Self::get_id();
247                let flags = unsafe {
248                    libobs::obs_get_source_output_flags(source_id.as_ptr().0)
249                };
250
251                if flags == 0 {
252                    return Err(libobs_wrapper::utils::ObsError::SourceNotAvailable(source_id.to_string()))
253                }
254
255                let mut hotkeys = libobs_wrapper::data::ObsData::new(runtime.clone())?;
256                let mut settings = libobs_wrapper::data::ObsData::new(runtime.clone())?;
257
258                Ok(Self {
259                    #(#struct_initializers,)*
260                    name,
261                    settings_updater: settings.bulk_update(),
262                    settings,
263                    hotkeys_updater: hotkeys.bulk_update(),
264                    hotkeys,
265                    runtime
266                })
267            }
268
269            fn runtime(&self) -> &libobs_wrapper::runtime::ObsRuntime {
270                &self.runtime
271            }
272
273            fn get_settings(&self) -> &libobs_wrapper::data::ObsData {
274                &self.settings
275            }
276
277            fn get_settings_updater(&mut self) -> &mut libobs_wrapper::data::ObsDataUpdater {
278                &mut self.settings_updater
279            }
280
281            fn get_hotkeys(&self) -> &libobs_wrapper::data::ObsData {
282                &self.hotkeys
283            }
284
285            fn get_hotkeys_updater(&mut self) -> &mut libobs_wrapper::data::ObsDataUpdater {
286                &mut self.hotkeys_updater
287            }
288
289            fn get_name(&self) -> libobs_wrapper::utils::ObsString {
290                self.name.clone()
291            }
292
293            fn get_id() -> libobs_wrapper::utils::ObsString {
294                #id_value.into()
295            }
296
297            fn object_build(self) -> Result<libobs_wrapper::utils::ObjectInfo, libobs_wrapper::utils::ObsError> {
298                let name = self.get_name();
299                let #builder_name {
300                    settings_updater,
301                    hotkeys_updater,
302                    settings,
303                    hotkeys,
304                    ..
305                } = self;
306
307                settings_updater.apply()?;
308                hotkeys_updater.apply()?;
309
310                Ok(libobs_wrapper::utils::ObjectInfo::new(
311                    Self::get_id(),
312                    name,
313                    Some(settings),
314                    Some(hotkeys),
315                ))
316            }
317        }
318
319        impl #builder_name {
320            #(#functions)*
321        }
322    };
323
324    TokenStream::from(expanded)
325}
326
327/// Implements the builder and updater logic for an OBS object.
328///
329/// This macro generates the implementation blocks for the builder and updater structs
330/// created by `obs_object_builder` and `obs_object_updater`. It should be applied
331/// to the implementation block of the main object struct.
332///
333/// # Example
334///
335/// ```ignore
336/// #[obs_object_impl]
337/// impl MySource {
338///     // Custom methods...
339/// }
340/// ```
341#[proc_macro_attribute]
342pub fn obs_object_impl(_attr: TokenStream, item: TokenStream) -> TokenStream {
343    let input = parse_macro_input!(item as ItemImpl);
344
345    // Extract the function from the implementation
346    let impl_item = input.items;
347    let impl_item2 = impl_item.clone();
348
349    // Create the builder and updater struct names
350    let base_name = if let Type::Path(TypePath { path, .. }) = &*input.self_ty {
351        path.segments.last().unwrap().ident.to_string()
352    } else {
353        panic!("Only path types are supported in self_ty")
354    };
355
356    let builder_name = format_ident!("{}Builder", base_name);
357    let updater_name = format_ident!("{}Updater", base_name);
358
359    let expanded = quote! {
360        // Builder implementation
361        impl #builder_name {
362            #(#impl_item)*
363        }
364
365        // Updater implementation with lifetime
366        impl<'a> #updater_name<'a> {
367            #(#impl_item2)*
368        }
369    };
370
371    TokenStream::from(expanded)
372}