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        #[cfg_attr(not(feature = "blocking"), async_trait::async_trait)]
58        impl <'a> libobs_wrapper::data::ObsObjectUpdater<'a> for #updater_name<'a> {
59            type ToUpdate = #updatable_type;
60
61            #[cfg_attr(feature = "blocking", remove_async_await::remove_async_await)]
62            async fn create_update(runtime: libobs_wrapper::runtime::ObsRuntime, updatable: &'a mut Self::ToUpdate) -> Result<Self, libobs_wrapper::utils::ObsError> {
63                let mut settings = libobs_wrapper::data::ObsData::new(runtime.clone()).await?;
64
65                Ok(Self {
66                    #(#struct_initializers,)*
67                    settings_updater: settings.bulk_update(),
68                    settings,
69                    updatable,
70                })
71            }
72
73            fn get_settings(&self) -> &libobs_wrapper::data::ObsData {
74                &self.settings
75            }
76
77            fn get_settings_updater(&mut self) -> &mut libobs_wrapper::data::ObsDataUpdater {
78                &mut self.settings_updater
79            }
80
81            fn get_id() -> libobs_wrapper::utils::ObsString {
82                #id_value.into()
83            }
84
85            #[cfg_attr(feature = "blocking", remove_async_await::remove_async_await)]
86            async fn update(self) -> Result<(), libobs_wrapper::utils::ObsError> {
87                use libobs_wrapper::utils::traits::ObsUpdatable;
88                let #updater_name {
89                    settings_updater,
90                    updatable,
91                    settings,
92                    ..
93                } = self;
94
95                log::trace!("Updating settings for {:?}", Self::get_id());
96                settings_updater.update().await?;
97
98                log::trace!("Updating raw settings for {:?}", Self::get_id());
99                let e = updatable.update_raw(settings).await;
100                log::trace!("Update done for {:?}", Self::get_id());
101
102                e
103            }
104        }
105
106        impl <'a> #updater_name <'a> {
107            #(#functions)*
108        }
109    };
110
111    TokenStream::from(expanded)
112}
113
114#[proc_macro_attribute]
115/// This macro is used to generate a builder pattern for an obs source. <br>
116/// The attribute should be the id of the source.<br>
117/// The struct should have named fields, each field should have an attribute `#[obs_property(type_t="your_type")]`. <br>
118/// `type_t` can be `enum`, `enum_string`, `string`, `bool` or `int`. <br>
119/// - `enum`: the field should be an enum with `num_derive::{FromPrimitive, ToPrimitive}`.
120/// - `enum_string`: the field should be an enum which implements `StringEnum`.
121/// - `string`: the field should be a string.
122/// - `bool`: the field should be a bool.
123/// - `type_t`: `int`, the field should be an i64.
124/// 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. <br>
125/// Documentation is inherited from the field to the setter function.<br>
126/// Example: <br>
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        #[cfg_attr(not(feature = "blocking"), async_trait::async_trait)]
221        impl libobs_wrapper::data::ObsObjectBuilder for #builder_name {
222            #[cfg_attr(feature = "blocking", remove_async_await::remove_async_await)]
223            async fn new<T: Into<libobs_wrapper::utils::ObsString> + Send + Sync>(name: T, runtime: libobs_wrapper::runtime::ObsRuntime) -> Result<Self, libobs_wrapper::utils::ObsError> {
224                let mut hotkeys = libobs_wrapper::data::ObsData::new(runtime.clone()).await?;
225                let mut settings = libobs_wrapper::data::ObsData::new(runtime.clone()).await?;
226
227                Ok(Self {
228                    #(#struct_initializers,)*
229                    name: name.into(),
230                    settings_updater: settings.bulk_update(),
231                    settings,
232                    hotkeys_updater: hotkeys.bulk_update(),
233                    hotkeys,
234                    runtime
235                })
236            }
237
238            fn get_settings(&self) -> &libobs_wrapper::data::ObsData {
239                &self.settings
240            }
241
242            fn get_settings_updater(&mut self) -> &mut libobs_wrapper::data::ObsDataUpdater {
243                &mut self.settings_updater
244            }
245
246            fn get_hotkeys(&self) -> &libobs_wrapper::data::ObsData {
247                &self.hotkeys
248            }
249
250            fn get_hotkeys_updater(&mut self) -> &mut libobs_wrapper::data::ObsDataUpdater {
251                &mut self.hotkeys_updater
252            }
253
254            fn get_name(&self) -> libobs_wrapper::utils::ObsString {
255                self.name.clone()
256            }
257
258            fn get_id() -> libobs_wrapper::utils::ObsString {
259                #id_value.into()
260            }
261
262            #[cfg_attr(feature = "blocking", remove_async_await::remove_async_await)]
263            async fn build(self) -> Result<libobs_wrapper::utils::ObjectInfo, libobs_wrapper::utils::ObsError> {
264                let name = self.get_name();
265                let #builder_name {
266                    settings_updater,
267                    hotkeys_updater,
268                    settings,
269                    hotkeys,
270                    ..
271                } = self;
272
273                settings_updater.update().await?;
274                hotkeys_updater.update().await?;
275
276                Ok(libobs_wrapper::utils::ObjectInfo::new(
277                    Self::get_id(),
278                    name,
279                    Some(settings),
280                    Some(hotkeys),
281                ))
282            }
283        }
284
285        impl #builder_name {
286            #(#functions)*
287        }
288    };
289
290    TokenStream::from(expanded)
291}
292
293#[proc_macro_attribute]
294pub fn obs_object_impl(_attr: TokenStream, item: TokenStream) -> TokenStream {
295    let input = parse_macro_input!(item as ItemImpl);
296
297    // Extract the function from the implementation
298    let impl_item = input.items;
299    let impl_item2 = impl_item.clone();
300
301    // Create the builder and updater struct names
302    let base_name = if let Type::Path(TypePath { path, .. }) = &*input.self_ty {
303        path.segments.last().unwrap().ident.to_string()
304    } else {
305        panic!("Only path types are supported in self_ty")
306    };
307
308    let builder_name = format_ident!("{}Builder", base_name);
309    let updater_name = format_ident!("{}Updater", base_name);
310
311    let expanded = quote! {
312        // Builder implementation
313        impl #builder_name {
314            #(#impl_item)*
315        }
316
317        // Updater implementation with lifetime
318        impl<'a> #updater_name<'a> {
319            #(#impl_item2)*
320        }
321    };
322
323    TokenStream::from(expanded)
324}