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)]
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
37    let input = parse_macro_input!(item as DeriveInput);
38
39    let i_ident = input.ident;
40    let updater_name = format_ident!("{}", i_ident);
41
42    let visibility = input.vis;
43    let attributes = input.attrs;
44
45    let fields = match input.data {
46        Data::Struct(data) => match data.fields {
47            Fields::Named(fields) => fields.named,
48            _ => panic!("Only named fields are supported"),
49        },
50        _ => panic!("Only structs are supported"),
51    };
52
53    let (struct_fields, struct_initializers) = fields::generate_struct_fields(&fields);
54    let functions = obs_properties_to_functions(
55        &fields,
56        quote! {
57            use libobs_wrapper::data::ObsObjectUpdater;
58            self.get_settings_updater()
59        },
60    );
61
62    let updatable_type2 = updatable_type.clone();
63    let expanded = quote! {
64        #(#attributes)*
65        #[allow(dead_code)]
66        #visibility struct #updater_name<'a> {
67            #(#struct_fields,)*
68            settings: libobs_wrapper::data::ObsData,
69            settings_updater: libobs_wrapper::data::ObsDataUpdater,
70            updatable: &'a mut #updatable_type2
71        }
72
73        impl <'a> libobs_wrapper::data::ObsObjectUpdater<'a> for #updater_name<'a> {
74            type ToUpdate = #updatable_type;
75
76            fn create_update(runtime: libobs_wrapper::runtime::ObsRuntime, updatable: &'a mut Self::ToUpdate) -> Result<Self, libobs_wrapper::utils::ObsError> {
77                let source_id = Self::get_id();
78                let flags = unsafe {
79                    libobs::obs_get_source_output_flags(source_id.as_ptr().0)
80                };
81
82                if flags == 0 {
83                    return Err(libobs_wrapper::utils::ObsError::SourceNotAvailable(source_id.to_string()))
84                }
85
86                let mut settings = libobs_wrapper::data::ObsData::new(runtime.clone())?;
87
88                Ok(Self {
89                    #(#struct_initializers,)*
90                    settings_updater: settings.bulk_update(),
91                    settings,
92                    updatable,
93                })
94            }
95
96            fn get_settings(&self) -> &libobs_wrapper::data::ObsData {
97                &self.settings
98            }
99
100            fn get_settings_updater(&mut self) -> &mut libobs_wrapper::data::ObsDataUpdater {
101                &mut self.settings_updater
102            }
103
104            fn get_id() -> libobs_wrapper::utils::ObsString {
105                #id_value.into()
106            }
107
108            fn update(self) -> Result<(), libobs_wrapper::utils::ObsError> {
109                use libobs_wrapper::utils::traits::ObsUpdatable;
110                let #updater_name {
111                    settings_updater,
112                    updatable,
113                    settings,
114                    ..
115                } = self;
116
117                log::trace!("Updating settings for {:?}", Self::get_id());
118                settings_updater.update()?;
119
120                log::trace!("Updating raw settings for {:?}", Self::get_id());
121                let e = updatable.update_raw(settings);
122                log::trace!("Update done for {:?}", Self::get_id());
123
124                e
125            }
126        }
127
128        impl <'a> #updater_name <'a> {
129            #(#functions)*
130        }
131    };
132
133    TokenStream::from(expanded)
134}
135
136#[proc_macro_attribute]
137/// Generates a builder struct for an OBS object (e.g., a source).
138///
139/// This macro creates a struct that implements `ObsObjectBuilder`, allowing you to configure
140/// and create new instances of an OBS object.
141///
142/// # Arguments
143///
144/// * `attr` - The unique ID of the OBS object (e.g., "window_capture").
145///
146/// # Fields
147///
148/// Each field in the struct must be annotated with `#[obs_property(type_t = "...")]`.
149/// Supported `type_t` values:
150///
151/// ```
152/// use libobs_wrapper::data::StringEnum;
153/// use libobs_simple_macro::obs_object_builder;
154/// use num_derive::{FromPrimitive, ToPrimitive};
155///
156/// #[repr(i32)]
157/// #[derive(Clone, Copy, Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)]
158/// pub enum ObsWindowCaptureMethod {
159///        MethodAuto = libobs::window_capture_method_METHOD_AUTO,
160///        MethodBitBlt = libobs::window_capture_method_METHOD_BITBLT,
161///        MethodWgc = libobs::window_capture_method_METHOD_WGC,
162/// }
163///
164/// #[derive(Clone, Copy, Debug, PartialEq, Eq)]
165/// pub enum ObsGameCaptureRgbaSpace {
166///     SRgb,
167///     RGBA2100pq
168/// }
169///
170/// impl StringEnum for ObsGameCaptureRgbaSpace {
171///     fn to_str(&self) -> &str {
172///         match self {
173///             ObsGameCaptureRgbaSpace::SRgb => "sRGB",
174///             ObsGameCaptureRgbaSpace::RGBA2100pq => "Rec. 2100 (PQ)"
175///         }
176///     }
177/// }
178///
179/// /// Provides an easy-to-use builder for the window capture source.
180/// #[derive(Debug)]
181/// #[obs_object_builder("window_capture")]
182/// pub struct WindowCaptureSourceBuilder {
183/// #[obs_property(type_t="enum")]
184///     /// Sets the capture method for the window capture
185///     capture_method: ObsWindowCaptureMethod,
186///
187/// ```ignore
188/// #[obs_object_builder("my_source")]
189/// pub struct MySourceBuilder {
190///     #[obs_property(type_t = "string")]
191///     pub url: String,
192/// }
193/// ```
194pub fn obs_object_builder(attr: TokenStream, item: TokenStream) -> TokenStream {
195    let id = parse_macro_input!(attr as LitStr);
196
197    let input = parse_macro_input!(item as DeriveInput);
198
199    let i_ident = input.ident;
200    let builder_name = format_ident!("{}", i_ident);
201
202    let generics = input.generics;
203    let visibility = input.vis;
204    let attributes = input.attrs;
205
206    let fields = match input.data {
207        Data::Struct(data) => match data.fields {
208            Fields::Named(fields) => fields.named,
209            _ => panic!("Only named fields are supported"),
210        },
211        _ => panic!("Only structs are supported"),
212    };
213
214    let id_value = id.value();
215    let (struct_fields, struct_initializers) = fields::generate_struct_fields(&fields);
216
217    let functions = obs_properties_to_functions(
218        &fields,
219        quote! {
220            use libobs_wrapper::data::ObsObjectBuilder;
221            self.get_settings_updater()
222        },
223    );
224
225    let expanded = quote! {
226        #(#attributes)*
227        #[allow(dead_code)]
228        #visibility struct #builder_name #generics {
229            #(#struct_fields,)*
230            settings: libobs_wrapper::data::ObsData,
231            settings_updater: libobs_wrapper::data::ObsDataUpdater,
232            hotkeys: libobs_wrapper::data::ObsData,
233            hotkeys_updater: libobs_wrapper::data::ObsDataUpdater,
234            name: libobs_wrapper::utils::ObsString,
235            runtime: libobs_wrapper::runtime::ObsRuntime
236        }
237
238        impl libobs_wrapper::data::ObsObjectBuilder for #builder_name {
239            fn new<T: Into<libobs_wrapper::utils::ObsString> + Send + Sync>(name: T, runtime: libobs_wrapper::runtime::ObsRuntime) -> Result<Self, libobs_wrapper::utils::ObsError> {
240                let name = name.into();
241                let source_id = Self::get_id();
242                let flags = unsafe {
243                    libobs::obs_get_source_output_flags(source_id.as_ptr().0)
244                };
245
246                if flags == 0 {
247                    return Err(libobs_wrapper::utils::ObsError::SourceNotAvailable(source_id.to_string()))
248                }
249
250                let mut hotkeys = libobs_wrapper::data::ObsData::new(runtime.clone())?;
251                let mut settings = libobs_wrapper::data::ObsData::new(runtime.clone())?;
252
253                Ok(Self {
254                    #(#struct_initializers,)*
255                    name,
256                    settings_updater: settings.bulk_update(),
257                    settings,
258                    hotkeys_updater: hotkeys.bulk_update(),
259                    hotkeys,
260                    runtime
261                })
262            }
263
264            fn get_settings(&self) -> &libobs_wrapper::data::ObsData {
265                &self.settings
266            }
267
268            fn get_settings_updater(&mut self) -> &mut libobs_wrapper::data::ObsDataUpdater {
269                &mut self.settings_updater
270            }
271
272            fn get_hotkeys(&self) -> &libobs_wrapper::data::ObsData {
273                &self.hotkeys
274            }
275
276            fn get_hotkeys_updater(&mut self) -> &mut libobs_wrapper::data::ObsDataUpdater {
277                &mut self.hotkeys_updater
278            }
279
280            fn get_name(&self) -> libobs_wrapper::utils::ObsString {
281                self.name.clone()
282            }
283
284            fn get_id() -> libobs_wrapper::utils::ObsString {
285                #id_value.into()
286            }
287
288            fn build(self) -> Result<libobs_wrapper::utils::ObjectInfo, libobs_wrapper::utils::ObsError> {
289                let name = self.get_name();
290                let #builder_name {
291                    settings_updater,
292                    hotkeys_updater,
293                    settings,
294                    hotkeys,
295                    ..
296                } = self;
297
298                settings_updater.update()?;
299                hotkeys_updater.update()?;
300
301                Ok(libobs_wrapper::utils::ObjectInfo::new(
302                    Self::get_id(),
303                    name,
304                    Some(settings),
305                    Some(hotkeys),
306                ))
307            }
308        }
309
310        impl #builder_name {
311            #(#functions)*
312        }
313    };
314
315    TokenStream::from(expanded)
316}
317
318/// Implements the builder and updater logic for an OBS object.
319///
320/// This macro generates the implementation blocks for the builder and updater structs
321/// created by `obs_object_builder` and `obs_object_updater`. It should be applied
322/// to the implementation block of the main object struct.
323///
324/// # Example
325///
326/// ```ignore
327/// #[obs_object_impl]
328/// impl MySource {
329///     // Custom methods...
330/// }
331/// ```
332#[proc_macro_attribute]
333pub fn obs_object_impl(_attr: TokenStream, item: TokenStream) -> TokenStream {
334    let input = parse_macro_input!(item as ItemImpl);
335
336    // Extract the function from the implementation
337    let impl_item = input.items;
338    let impl_item2 = impl_item.clone();
339
340    // Create the builder and updater struct names
341    let base_name = if let Type::Path(TypePath { path, .. }) = &*input.self_ty {
342        path.segments.last().unwrap().ident.to_string()
343    } else {
344        panic!("Only path types are supported in self_ty")
345    };
346
347    let builder_name = format_ident!("{}Builder", base_name);
348    let updater_name = format_ident!("{}Updater", base_name);
349
350    let expanded = quote! {
351        // Builder implementation
352        impl #builder_name {
353            #(#impl_item)*
354        }
355
356        // Updater implementation with lifetime
357        impl<'a> #updater_name<'a> {
358            #(#impl_item2)*
359        }
360    };
361
362    TokenStream::from(expanded)
363}