libobs_source_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#[proc_macro_attribute]
13//TODO more documents here
14/// This macro is used to generate an updater pattern for an obs object (for example a source).
15/// For more examples look at libobs-sources
16pub fn obs_object_updater(attr: TokenStream, item: TokenStream) -> TokenStream {
17    let u_input = parse_macro_input!(attr as UpdaterInput);
18    let id_value = u_input.name.value();
19    let updatable_type = u_input.updatable_type;
20
21    let input = parse_macro_input!(item as DeriveInput);
22
23    let i_ident = input.ident;
24    let updater_name = format_ident!("{}", i_ident);
25
26    let visibility = input.vis;
27    let attributes = input.attrs;
28
29    let fields = match input.data {
30        Data::Struct(data) => match data.fields {
31            Fields::Named(fields) => fields.named,
32            _ => panic!("Only named fields are supported"),
33        },
34        _ => panic!("Only structs are supported"),
35    };
36
37    let (struct_fields, struct_initializers) = fields::generate_struct_fields(&fields);
38    let functions = obs_properties_to_functions(
39        &fields,
40        quote! {
41            use libobs_wrapper::data::ObsObjectUpdater;
42            self.get_settings_updater()
43        },
44    );
45
46    let updatable_type2 = updatable_type.clone();
47    let expanded = quote! {
48        #(#attributes)*
49        #[allow(dead_code)]
50        #visibility struct #updater_name<'a> {
51            #(#struct_fields,)*
52            settings: libobs_wrapper::data::ObsData,
53            settings_updater: libobs_wrapper::data::ObsDataUpdater,
54            updatable: &'a mut #updatable_type2
55        }
56
57        impl <'a> libobs_wrapper::data::ObsObjectUpdater<'a> for #updater_name<'a> {
58            type ToUpdate = #updatable_type;
59
60            fn create_update(runtime: libobs_wrapper::runtime::ObsRuntime, updatable: &'a mut Self::ToUpdate) -> Result<Self, libobs_wrapper::utils::ObsError> {
61                let mut settings = libobs_wrapper::data::ObsData::new(runtime.clone())?;
62
63                Ok(Self {
64                    #(#struct_initializers,)*
65                    settings_updater: settings.bulk_update(),
66                    settings,
67                    updatable,
68                })
69            }
70
71            fn get_settings(&self) -> &libobs_wrapper::data::ObsData {
72                &self.settings
73            }
74
75            fn get_settings_updater(&mut self) -> &mut libobs_wrapper::data::ObsDataUpdater {
76                &mut self.settings_updater
77            }
78
79            fn get_id() -> libobs_wrapper::utils::ObsString {
80                #id_value.into()
81            }
82
83            fn update(self) -> Result<(), libobs_wrapper::utils::ObsError> {
84                use libobs_wrapper::utils::traits::ObsUpdatable;
85                let #updater_name {
86                    settings_updater,
87                    updatable,
88                    settings,
89                    ..
90                } = self;
91
92                log::trace!("Updating settings for {:?}", Self::get_id());
93                settings_updater.update()?;
94
95                log::trace!("Updating raw settings for {:?}", Self::get_id());
96                let e = updatable.update_raw(settings);
97                log::trace!("Update done for {:?}", Self::get_id());
98
99                e
100            }
101        }
102
103        impl <'a> #updater_name <'a> {
104            #(#functions)*
105        }
106    };
107
108    TokenStream::from(expanded)
109}
110
111#[proc_macro_attribute]
112/// This macro is used to generate a builder pattern for an obs source. <br>
113/// The attribute should be the id of the source.<br>
114/// The struct should have named fields, each field should have an attribute `#[obs_property(type_t="your_type")]`. <br>
115/// `type_t` can be `enum`, `enum_string`, `string`, `bool` or `int`. <br>
116/// - `enum`: the field should be an enum with `num_derive::{FromPrimitive, ToPrimitive}`.
117/// - `enum_string`: the field should be an enum which implements `StringEnum`.
118/// - `string`: the field should be a string.
119/// - `bool`: the field should be a bool.
120/// - `type_t`: `int`, the field should be an i64.
121///   The attribute can also have a `settings_key` which is the key used in the settings, if this attribute is not given, the macro defaults to the field name.
122///
123/// Documentation is inherited from the field to the setter function.
124///
125/// Example:
126///
127/// ```
128/// use libobs_wrapper::data::StringEnum;
129/// use libobs_source_macro::obs_object_builder;
130/// use num_derive::{FromPrimitive, ToPrimitive};
131///
132/// #[repr(i32)]
133/// #[derive(Clone, Copy, Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)]
134/// pub enum ObsWindowCaptureMethod {
135///        MethodAuto = libobs::window_capture_method_METHOD_AUTO,
136///        MethodBitBlt = libobs::window_capture_method_METHOD_BITBLT,
137///        MethodWgc = libobs::window_capture_method_METHOD_WGC,
138/// }
139///
140/// #[derive(Clone, Copy, Debug, PartialEq, Eq)]
141/// pub enum ObsGameCaptureRgbaSpace {
142///     SRgb,
143///     RGBA2100pq
144/// }
145///
146/// impl StringEnum for ObsGameCaptureRgbaSpace {
147///     fn to_str(&self) -> &str {
148///         match self {
149///             ObsGameCaptureRgbaSpace::SRgb => "sRGB",
150///             ObsGameCaptureRgbaSpace::RGBA2100pq => "Rec. 2100 (PQ)"
151///         }
152///     }
153/// }
154///
155/// /// Provides a easy to use builder for the window capture source.
156/// #[derive(Debug)]
157/// #[obs_object_builder("window_capture")]
158/// pub struct WindowCaptureSourceBuilder {
159/// #[obs_property(type_t="enum")]
160///     /// Sets the capture method for the window capture
161///     capture_method: ObsWindowCaptureMethod,
162///
163///     /// Sets the window to capture.
164///     #[obs_property(type_t = "string", settings_key = "window")]
165///     window_raw: String,
166///
167///     #[obs_property(type_t = "bool")]
168///     /// Sets whether the cursor should be captured
169///     cursor: bool,
170///
171///     /// Sets the capture mode for the game capture source. Look at doc for `ObsGameCaptureMode`
172///     #[obs_property(type_t = "enum_string")]
173///     capture_mode: ObsGameCaptureMode,
174/// }
175/// ```
176pub fn obs_object_builder(attr: TokenStream, item: TokenStream) -> TokenStream {
177    let id = parse_macro_input!(attr as LitStr);
178
179    let input = parse_macro_input!(item as DeriveInput);
180
181    let i_ident = input.ident;
182    let builder_name = format_ident!("{}", i_ident);
183
184    let generics = input.generics;
185    let visibility = input.vis;
186    let attributes = input.attrs;
187
188    let fields = match input.data {
189        Data::Struct(data) => match data.fields {
190            Fields::Named(fields) => fields.named,
191            _ => panic!("Only named fields are supported"),
192        },
193        _ => panic!("Only structs are supported"),
194    };
195
196    let id_value = id.value();
197    let (struct_fields, struct_initializers) = fields::generate_struct_fields(&fields);
198
199    let functions = obs_properties_to_functions(
200        &fields,
201        quote! {
202            use libobs_wrapper::data::ObsObjectBuilder;
203            self.get_settings_updater()
204        },
205    );
206
207    let expanded = quote! {
208        #(#attributes)*
209        #[allow(dead_code)]
210        #visibility struct #builder_name #generics {
211            #(#struct_fields,)*
212            settings: libobs_wrapper::data::ObsData,
213            settings_updater: libobs_wrapper::data::ObsDataUpdater,
214            hotkeys: libobs_wrapper::data::ObsData,
215            hotkeys_updater: libobs_wrapper::data::ObsDataUpdater,
216            name: libobs_wrapper::utils::ObsString,
217            runtime: libobs_wrapper::runtime::ObsRuntime
218        }
219
220        impl libobs_wrapper::data::ObsObjectBuilder for #builder_name {
221            fn new<T: Into<libobs_wrapper::utils::ObsString> + Send + Sync>(name: T, runtime: libobs_wrapper::runtime::ObsRuntime) -> Result<Self, libobs_wrapper::utils::ObsError> {
222                let mut hotkeys = libobs_wrapper::data::ObsData::new(runtime.clone())?;
223                let mut settings = libobs_wrapper::data::ObsData::new(runtime.clone())?;
224
225                Ok(Self {
226                    #(#struct_initializers,)*
227                    name: name.into(),
228                    settings_updater: settings.bulk_update(),
229                    settings,
230                    hotkeys_updater: hotkeys.bulk_update(),
231                    hotkeys,
232                    runtime
233                })
234            }
235
236            fn get_settings(&self) -> &libobs_wrapper::data::ObsData {
237                &self.settings
238            }
239
240            fn get_settings_updater(&mut self) -> &mut libobs_wrapper::data::ObsDataUpdater {
241                &mut self.settings_updater
242            }
243
244            fn get_hotkeys(&self) -> &libobs_wrapper::data::ObsData {
245                &self.hotkeys
246            }
247
248            fn get_hotkeys_updater(&mut self) -> &mut libobs_wrapper::data::ObsDataUpdater {
249                &mut self.hotkeys_updater
250            }
251
252            fn get_name(&self) -> libobs_wrapper::utils::ObsString {
253                self.name.clone()
254            }
255
256            fn get_id() -> libobs_wrapper::utils::ObsString {
257                #id_value.into()
258            }
259
260            fn build(self) -> Result<libobs_wrapper::utils::ObjectInfo, libobs_wrapper::utils::ObsError> {
261                let name = self.get_name();
262                let #builder_name {
263                    settings_updater,
264                    hotkeys_updater,
265                    settings,
266                    hotkeys,
267                    ..
268                } = self;
269
270                settings_updater.update()?;
271                hotkeys_updater.update()?;
272
273                Ok(libobs_wrapper::utils::ObjectInfo::new(
274                    Self::get_id(),
275                    name,
276                    Some(settings),
277                    Some(hotkeys),
278                ))
279            }
280        }
281
282        impl #builder_name {
283            #(#functions)*
284        }
285    };
286
287    TokenStream::from(expanded)
288}
289
290#[proc_macro_attribute]
291pub fn obs_object_impl(_attr: TokenStream, item: TokenStream) -> TokenStream {
292    let input = parse_macro_input!(item as ItemImpl);
293
294    // Extract the function from the implementation
295    let impl_item = input.items;
296    let impl_item2 = impl_item.clone();
297
298    // Create the builder and updater struct names
299    let base_name = if let Type::Path(TypePath { path, .. }) = &*input.self_ty {
300        path.segments.last().unwrap().ident.to_string()
301    } else {
302        panic!("Only path types are supported in self_ty")
303    };
304
305    let builder_name = format_ident!("{}Builder", base_name);
306    let updater_name = format_ident!("{}Updater", base_name);
307
308    let expanded = quote! {
309        // Builder implementation
310        impl #builder_name {
311            #(#impl_item)*
312        }
313
314        // Updater implementation with lifetime
315        impl<'a> #updater_name<'a> {
316            #(#impl_item2)*
317        }
318    };
319
320    TokenStream::from(expanded)
321}