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_mut()
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            updatable: &'a mut #updatable_type2
54        }
55
56        impl <'a> libobs_wrapper::data::ObsObjectUpdater<'a> for #updater_name<'a> {
57            type ToUpdate = #updatable_type;
58
59            fn create_update(updatable: &'a mut Self::ToUpdate) -> Self {
60                Self {
61                    #(#struct_initializers,)*
62                    settings: libobs_wrapper::data::ObsData::new(),
63                    updatable,
64                }
65            }
66
67            fn get_settings(&self) -> &libobs_wrapper::data::ObsData {
68                &self.settings
69            }
70
71            fn get_settings_mut(&mut self) -> &mut libobs_wrapper::data::ObsData {
72                &mut self.settings
73            }
74
75            fn get_id() -> libobs_wrapper::utils::ObsString {
76                #id_value.into()
77            }
78
79            fn update(self) {
80                use libobs_wrapper::utils::traits::ObsUpdatable;
81                let settings = self.settings;
82                self.updatable.update_raw(settings);
83            }
84        }
85
86        impl <'a> #updater_name <'a> {
87            #(#functions)*
88        }
89    };
90
91    TokenStream::from(expanded)
92}
93
94#[proc_macro_attribute]
95/// This macro is used to generate a builder pattern for an obs source. <br>
96/// The attribute should be the id of the source.<br>
97/// The struct should have named fields, each field should have an attribute `#[obs_property(type_t="your_type")]`. <br>
98/// `type_t` can be `enum`, `enum_string`, `string`, `bool` or `int`. <br>
99/// - `enum`: the field should be an enum with `num_derive::{FromPrimitive, ToPrimitive}`.
100/// - `enum_string`: the field should be an enum which implements `StringEnum`.
101/// - `string`: the field should be a string.
102/// - `bool`: the field should be a bool.
103/// - `type_t`: `int`, the field should be an i64.
104/// 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>
105/// Documentation is inherited from the field to the setter function.<br>
106/// Example: <br>
107/// ```
108/// use libobs_wrapper::data::StringEnum;
109/// use libobs_source_macro::obs_object_builder;
110/// use num_derive::{FromPrimitive, ToPrimitive};
111///
112/// #[repr(i32)]
113/// #[derive(Clone, Copy, Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)]
114/// pub enum ObsWindowCaptureMethod {
115///     MethodAuto = libobs::window_capture_method_METHOD_AUTO,
116/// 	MethodBitBlt = libobs::window_capture_method_METHOD_BITBLT,
117/// 	MethodWgc = libobs::window_capture_method_METHOD_WGC,
118/// }
119///
120/// #[derive(Clone, Copy, Debug, PartialEq, Eq)]
121/// pub enum ObsGameCaptureRgbaSpace {
122///     SRgb,
123///     RGBA2100pq
124/// }
125///
126/// impl StringEnum for ObsGameCaptureRgbaSpace {
127///     fn to_str(&self) -> &str {
128///         match self {
129///             ObsGameCaptureRgbaSpace::SRgb => "sRGB",
130///             ObsGameCaptureRgbaSpace::RGBA2100pq => "Rec. 2100 (PQ)"
131///         }
132///     }
133/// }
134///
135/// /// Provides a easy to use builder for the window capture source.
136/// #[derive(Debug)]
137/// #[obs_object_builder("window_capture")]
138/// pub struct WindowCaptureSourceBuilder {
139/// #[obs_property(type_t="enum")]
140///     /// Sets the capture method for the window capture
141///     capture_method: ObsWindowCaptureMethod,
142///
143///     /// Sets the window to capture.
144///     #[obs_property(type_t = "string", settings_key = "window")]
145///     window_raw: String,
146///
147///     #[obs_property(type_t = "bool")]
148///     /// Sets whether the cursor should be captured
149///     cursor: bool,
150///
151///     /// Sets the capture mode for the game capture source. Look at doc for `ObsGameCaptureMode`
152///     #[obs_property(type_t = "enum_string")]
153///     capture_mode: ObsGameCaptureMode,
154/// }
155/// ```
156pub fn obs_object_builder(attr: TokenStream, item: TokenStream) -> TokenStream {
157    let id = parse_macro_input!(attr as LitStr);
158
159    let input = parse_macro_input!(item as DeriveInput);
160
161    let i_ident = input.ident;
162    let builder_name = format_ident!("{}", i_ident);
163
164    let generics = input.generics;
165    let visibility = input.vis;
166    let attributes = input.attrs;
167
168    let fields = match input.data {
169        Data::Struct(data) => match data.fields {
170            Fields::Named(fields) => fields.named,
171            _ => panic!("Only named fields are supported"),
172        },
173        _ => panic!("Only structs are supported"),
174    };
175
176    let id_value = id.value();
177    let (struct_fields, struct_initializers) = fields::generate_struct_fields(&fields);
178
179    let functions = obs_properties_to_functions(
180        &fields,
181        quote! {
182            use libobs_wrapper::data::ObsObjectBuilder;
183            self.get_or_create_settings()
184        },
185    );
186
187    let expanded = quote! {
188        #(#attributes)*
189        #[allow(dead_code)]
190        #visibility struct #builder_name #generics {
191            #(#struct_fields,)*
192            settings: Option<libobs_wrapper::data::ObsData>,
193            hotkeys: Option<libobs_wrapper::data::ObsData>,
194            name: libobs_wrapper::utils::ObsString
195        }
196
197        impl libobs_wrapper::data::ObsObjectBuilder for #builder_name {
198            fn new(name: impl Into<libobs_wrapper::utils::ObsString>) -> Self {
199                Self {
200                    #(#struct_initializers,)*
201                    name: name.into(),
202                    hotkeys: None,
203                    settings: None
204                }
205            }
206
207            fn get_settings(&self) -> &Option<libobs_wrapper::data::ObsData> {
208                &self.settings
209            }
210
211            fn get_settings_mut(&mut self) -> &mut Option<libobs_wrapper::data::ObsData> {
212                &mut self.settings
213            }
214
215            fn get_hotkeys(&self) -> &Option<libobs_wrapper::data::ObsData> {
216                &self.hotkeys
217            }
218
219            fn get_hotkeys_mut(&mut self) -> &mut Option<libobs_wrapper::data::ObsData> {
220                &mut self.hotkeys
221            }
222
223            fn get_name(&self) -> libobs_wrapper::utils::ObsString {
224                self.name.clone()
225            }
226
227            fn get_id() -> libobs_wrapper::utils::ObsString {
228                #id_value.into()
229            }
230        }
231
232        impl #builder_name {
233            #(#functions)*
234        }
235    };
236
237    TokenStream::from(expanded)
238}
239
240#[proc_macro_attribute]
241pub fn obs_object_impl(_attr: TokenStream, item: TokenStream) -> TokenStream {
242    let input = parse_macro_input!(item as ItemImpl);
243
244    // Extract the function from the implementation
245    let impl_item = input.items;
246    let impl_item2 = impl_item.clone();
247
248    // Create the builder and updater struct names
249    let base_name = if let Type::Path(TypePath { path, .. }) = &*input.self_ty {
250        path.segments.last().unwrap().ident.to_string()
251    } else {
252        panic!("Only path types are supported in self_ty")
253    };
254
255    let builder_name = format_ident!("{}Builder", base_name);
256    let updater_name = format_ident!("{}Updater", base_name);
257
258    let expanded = quote! {
259        // Builder implementation
260        impl #builder_name {
261            #(#impl_item)*
262        }
263
264        // Updater implementation with lifetime
265        impl<'a> #updater_name<'a> {
266            #(#impl_item2)*
267        }
268    };
269
270    TokenStream::from(expanded)
271}