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");