rea_rs_macros/lib.rs
1#![doc(html_root_url = "https://docs.rs/rea-rs-macros/0.1.31")]
2#![allow(renamed_and_removed_lints)]
3#![deny(broken_intra_doc_links)]
4
5//! This crate is part of [reaper-rs](https://github.com/helgoboss/reaper-rs) and contains a
6//! [simple attribute macro](attr.reaper_extension_plugin.html) to simplify
7//! bootstrapping REAPER extension plug-ins.
8use darling::FromMeta;
9use proc_macro::TokenStream;
10use quote::quote;
11
12/// Macro for easily bootstrapping a REAPER extension plug-in.
13///
14/// Use the macro like this:
15/// ```ignore
16/// use std::error::Error;
17/// use rea_rs_macros::reaper_extension_plugin;
18/// use rea_rs_low::PluginContext;
19/// use rea_rs::Reaper;
20///
21/// #[reaper_extension_plugin]
22/// fn plugin_main(context: PluginContext) -> Result<(), Box<dyn Error>> {
23/// let reaper = Reaper::init_global(context);
24/// reaper.show_console_msg("Hello world from rea-rs API!");
25/// Ok(())
26/// }
27/// ```
28#[proc_macro_attribute]
29pub fn reaper_extension_plugin(
30 attr: TokenStream,
31 input: TokenStream,
32) -> TokenStream {
33 // Parse attributes
34 let args = syn::parse_macro_input!(attr as syn::AttributeArgs);
35 let _args = match ReaperExtensionPluginMacroArgs::from_list(&args) {
36 Ok(v) => v,
37 Err(e) => {
38 return e.write_errors().into();
39 }
40 };
41 // Parse function which is annotated with that attribute
42 let main_function = syn::parse_macro_input!(input as syn::ItemFn);
43 // Check if it's a low-level or high-level plug-in.
44 // If the function has one parameter, it's a low-level plug-in, otherwise a
45 // high-level one.
46 match main_function.sig.inputs.len() {
47 1 => {
48 // One function parameter. Must be a low-level plug-in.
49 generate_low_level_plugin_code(main_function)
50 }
51 _ => panic!("REAPER extension plugin function must have "),
52 }
53}
54
55fn generate_low_level_plugin_code(main_function: syn::ItemFn) -> TokenStream {
56 let main_function_name = &main_function.sig.ident;
57 let tokens = quote! {
58 /// Windows entry and exit point for clean-up.
59 ///
60 /// Called by REAPER for Windows once at startup time with DLL_PROCESS_ATTACH and once
61 /// at exit time or manual unload time (if plug-in initialization failed) with
62 /// DLL_PROCESS_DETACH.
63 #[cfg(target_family = "windows")]
64 #[allow(non_snake_case)]
65 #[no_mangle]
66 extern "system" fn DllMain(
67 hinstance: rea_rs_low::raw::HINSTANCE,
68 reason: u32,
69 _: *const u8,
70 ) -> u32 {
71 if (reason == rea_rs_low::raw::DLL_PROCESS_DETACH) {
72 unsafe {
73 rea_rs_low::execute_plugin_destroy_hooks();
74 }
75 }
76 1
77 }
78
79 /// Linux entry and exit point for getting hold of the SWELL function provider.
80 ///
81 /// See `reaper_vst_plugin!` macro why clean-up is neither necessary nor desired on Linux
82 /// at the moment.
83 ///
84 /// Called by REAPER for Linux once at startup time with DLL_PROCESS_ATTACH and once
85 /// at exit time or manual unload time (if plug-in initialization failed) with
86 /// DLL_PROCESS_DETACH.
87 ///
88 /// In case anybody wonders where's the SWELL entry point for macOS:
89 /// `swell-modstub-custom.mm`.
90 #[cfg(target_os = "linux")]
91 #[allow(non_snake_case)]
92 #[no_mangle]
93 extern "C" fn SWELL_dllMain(
94 hinstance: rea_rs_low::raw::HINSTANCE,
95 reason: u32,
96 get_func: Option<
97 unsafe extern "C" fn(
98 name: *const std::os::raw::c_char,
99 ) -> *mut std::os::raw::c_void,
100 >,
101 ) -> std::os::raw::c_int {
102 if (reason == rea_rs_low::raw::DLL_PROCESS_ATTACH) {
103 rea_rs_low::register_swell_function_provider(get_func);
104 }
105 1
106 }
107
108 /// Entry point for the REAPER extension plug-in.
109 ///
110 /// This is called by REAPER at startup time.
111 #[no_mangle]
112 unsafe extern "C" fn ReaperPluginEntry(h_instance: ::rea_rs_low::raw::HINSTANCE, rec: *mut ::rea_rs_low::raw::reaper_plugin_info_t) -> ::std::os::raw::c_int {
113 let static_context = rea_rs_low::static_extension_plugin_context();
114 ::rea_rs_low::bootstrap_extension_plugin(h_instance, rec, static_context, #main_function_name)
115 }
116
117 #main_function
118 };
119 tokens.into()
120}
121
122/// Arguments passed to the [`reaper_extension_plugin`] macro.
123///
124/// [`reaper_extension_plugin`]: macro.reaper_extension_plugin.html
125#[derive(Default, Debug, FromMeta)]
126#[darling(default)]
127struct ReaperExtensionPluginMacroArgs {
128 /// Plug-in name which will appear in error reports.
129 ///
130 /// Only used for high-level plug-ins. Optional, defaults to package name.
131 name: Option<String>,
132 /// Support e-mail address which will appear in error reports.
133 ///
134 /// Necessary for high-level plug-in.
135 support_email_address: Option<String>,
136}
137
138// #[cfg(doctest)]
139// doc_comment::doctest!("../../README.md");