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. <br>
122/// Documentation is inherited from the field to the setter function.<br>
123/// Example: <br>
124/// ```
125/// use libobs_wrapper::data::StringEnum;
126/// use libobs_source_macro::obs_object_builder;
127/// use num_derive::{FromPrimitive, ToPrimitive};
128///
129/// #[repr(i32)]
130/// #[derive(Clone, Copy, Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)]
131/// pub enum ObsWindowCaptureMethod {
132///     MethodAuto = libobs::window_capture_method_METHOD_AUTO,
133/// 	MethodBitBlt = libobs::window_capture_method_METHOD_BITBLT,
134/// 	MethodWgc = libobs::window_capture_method_METHOD_WGC,
135/// }
136///
137/// #[derive(Clone, Copy, Debug, PartialEq, Eq)]
138/// pub enum ObsGameCaptureRgbaSpace {
139///     SRgb,
140///     RGBA2100pq
141/// }
142///
143/// impl StringEnum for ObsGameCaptureRgbaSpace {
144///     fn to_str(&self) -> &str {
145///         match self {
146///             ObsGameCaptureRgbaSpace::SRgb => "sRGB",
147///             ObsGameCaptureRgbaSpace::RGBA2100pq => "Rec. 2100 (PQ)"
148///         }
149///     }
150/// }
151///
152/// /// Provides a easy to use builder for the window capture source.
153/// #[derive(Debug)]
154/// #[obs_object_builder("window_capture")]
155/// pub struct WindowCaptureSourceBuilder {
156/// #[obs_property(type_t="enum")]
157///     /// Sets the capture method for the window capture
158///     capture_method: ObsWindowCaptureMethod,
159///
160///     /// Sets the window to capture.
161///     #[obs_property(type_t = "string", settings_key = "window")]
162///     window_raw: String,
163///
164///     #[obs_property(type_t = "bool")]
165///     /// Sets whether the cursor should be captured
166///     cursor: bool,
167///
168///     /// Sets the capture mode for the game capture source. Look at doc for `ObsGameCaptureMode`
169///     #[obs_property(type_t = "enum_string")]
170///     capture_mode: ObsGameCaptureMode,
171/// }
172/// ```
173pub fn obs_object_builder(attr: TokenStream, item: TokenStream) -> TokenStream {
174    let id = parse_macro_input!(attr as LitStr);
175
176    let input = parse_macro_input!(item as DeriveInput);
177
178    let i_ident = input.ident;
179    let builder_name = format_ident!("{}", i_ident);
180
181    let generics = input.generics;
182    let visibility = input.vis;
183    let attributes = input.attrs;
184
185    let fields = match input.data {
186        Data::Struct(data) => match data.fields {
187            Fields::Named(fields) => fields.named,
188            _ => panic!("Only named fields are supported"),
189        },
190        _ => panic!("Only structs are supported"),
191    };
192
193    let id_value = id.value();
194    let (struct_fields, struct_initializers) = fields::generate_struct_fields(&fields);
195
196    let functions = obs_properties_to_functions(
197        &fields,
198        quote! {
199            use libobs_wrapper::data::ObsObjectBuilder;
200            self.get_settings_updater()
201        },
202    );
203
204    let expanded = quote! {
205        #(#attributes)*
206        #[allow(dead_code)]
207        #visibility struct #builder_name #generics {
208            #(#struct_fields,)*
209            settings: libobs_wrapper::data::ObsData,
210            settings_updater: libobs_wrapper::data::ObsDataUpdater,
211            hotkeys: libobs_wrapper::data::ObsData,
212            hotkeys_updater: libobs_wrapper::data::ObsDataUpdater,
213            name: libobs_wrapper::utils::ObsString,
214            runtime: libobs_wrapper::runtime::ObsRuntime
215        }
216
217        impl libobs_wrapper::data::ObsObjectBuilder for #builder_name {
218            fn new<T: Into<libobs_wrapper::utils::ObsString> + Send + Sync>(name: T, runtime: libobs_wrapper::runtime::ObsRuntime) -> Result<Self, libobs_wrapper::utils::ObsError> {
219                let mut hotkeys = libobs_wrapper::data::ObsData::new(runtime.clone())?;
220                let mut settings = libobs_wrapper::data::ObsData::new(runtime.clone())?;
221
222                Ok(Self {
223                    #(#struct_initializers,)*
224                    name: name.into(),
225                    settings_updater: settings.bulk_update(),
226                    settings,
227                    hotkeys_updater: hotkeys.bulk_update(),
228                    hotkeys,
229                    runtime
230                })
231            }
232
233            fn get_settings(&self) -> &libobs_wrapper::data::ObsData {
234                &self.settings
235            }
236
237            fn get_settings_updater(&mut self) -> &mut libobs_wrapper::data::ObsDataUpdater {
238                &mut self.settings_updater
239            }
240
241            fn get_hotkeys(&self) -> &libobs_wrapper::data::ObsData {
242                &self.hotkeys
243            }
244
245            fn get_hotkeys_updater(&mut self) -> &mut libobs_wrapper::data::ObsDataUpdater {
246                &mut self.hotkeys_updater
247            }
248
249            fn get_name(&self) -> libobs_wrapper::utils::ObsString {
250                self.name.clone()
251            }
252
253            fn get_id() -> libobs_wrapper::utils::ObsString {
254                #id_value.into()
255            }
256
257            fn build(self) -> Result<libobs_wrapper::utils::ObjectInfo, libobs_wrapper::utils::ObsError> {
258                let name = self.get_name();
259                let #builder_name {
260                    settings_updater,
261                    hotkeys_updater,
262                    settings,
263                    hotkeys,
264                    ..
265                } = self;
266
267                settings_updater.update()?;
268                hotkeys_updater.update()?;
269
270                Ok(libobs_wrapper::utils::ObjectInfo::new(
271                    Self::get_id(),
272                    name,
273                    Some(settings),
274                    Some(hotkeys),
275                ))
276            }
277        }
278
279        impl #builder_name {
280            #(#functions)*
281        }
282    };
283
284    TokenStream::from(expanded)
285}
286
287#[proc_macro_attribute]
288pub fn obs_object_impl(_attr: TokenStream, item: TokenStream) -> TokenStream {
289    let input = parse_macro_input!(item as ItemImpl);
290
291    // Extract the function from the implementation
292    let impl_item = input.items;
293    let impl_item2 = impl_item.clone();
294
295    // Create the builder and updater struct names
296    let base_name = if let Type::Path(TypePath { path, .. }) = &*input.self_ty {
297        path.segments.last().unwrap().ident.to_string()
298    } else {
299        panic!("Only path types are supported in self_ty")
300    };
301
302    let builder_name = format_ident!("{}Builder", base_name);
303    let updater_name = format_ident!("{}Updater", base_name);
304
305    let expanded = quote! {
306        // Builder implementation
307        impl #builder_name {
308            #(#impl_item)*
309        }
310
311        // Updater implementation with lifetime
312        impl<'a> #updater_name<'a> {
313            #(#impl_item2)*
314        }
315    };
316
317    TokenStream::from(expanded)
318}