tauri_macros/
lib.rs

1// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5//! Create macros for `tauri::Context`, invoke handler and commands leveraging the `tauri-codegen` crate.
6
7#![doc(
8  html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png",
9  html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png"
10)]
11
12use std::path::PathBuf;
13
14use crate::context::ContextItems;
15use proc_macro::TokenStream;
16use quote::{quote, ToTokens};
17use syn::{parse2, parse_macro_input, LitStr};
18use tauri_codegen::image::CachedIcon;
19
20mod command;
21mod menu;
22mod mobile;
23mod runtime;
24
25#[macro_use]
26mod context;
27
28/// Mark a function as a command handler. It creates a wrapper function with the necessary glue code.
29///
30/// # Stability
31/// The output of this macro is managed internally by Tauri,
32/// and should not be accessed directly on normal applications.
33/// It may have breaking changes in the future.
34#[proc_macro_attribute]
35pub fn command(attributes: TokenStream, item: TokenStream) -> TokenStream {
36  command::wrapper(attributes, item)
37}
38
39#[proc_macro_attribute]
40pub fn mobile_entry_point(attributes: TokenStream, item: TokenStream) -> TokenStream {
41  mobile::entry_point(attributes, item)
42}
43
44/// Accepts a list of command functions. Creates a handler that allows commands to be called from JS with invoke().
45///
46/// You can optionally annotate the commands with a inner attribute tag `#![plugin(your_plugin_name)]`
47/// for `build > removeUnusedCommands` to work for plugins not defined in a standalone crate like `tauri-plugin-fs`
48///
49/// # Examples
50///
51/// ```rust,ignore
52/// use tauri_macros::{command, generate_handler};
53/// #[command]
54/// fn command_one() {
55///   println!("command one called");
56/// }
57/// #[command]
58/// fn command_two() {
59///   println!("command two called");
60/// }
61/// fn main() {
62///   let _handler = generate_handler![command_one, command_two];
63/// }
64/// ```
65///
66/// # Stability
67///
68/// The output of this macro is managed internally by Tauri,
69/// and should not be accessed directly on normal applications.
70/// It may have breaking changes in the future.
71#[proc_macro]
72pub fn generate_handler(item: TokenStream) -> TokenStream {
73  parse_macro_input!(item as command::Handler).into()
74}
75
76/// Reads a Tauri config file and generates a `::tauri::Context` based on the content.
77///
78/// # Stability
79/// The output of this macro is managed internally by Tauri,
80/// and should not be accessed directly on normal applications.
81/// It may have breaking changes in the future.
82#[proc_macro]
83pub fn generate_context(items: TokenStream) -> TokenStream {
84  // this macro is exported from the context module
85  let path = parse_macro_input!(items as ContextItems);
86  context::generate_context(path).into()
87}
88
89/// Adds the default type for the last parameter (assumed to be runtime) for a specific feature.
90///
91/// e.g. To default the runtime generic to type `crate::Wry` when the `wry` feature is enabled, the
92/// syntax would look like `#[default_runtime(crate::Wry, wry)`. This is **always** set for the last
93/// generic, so make sure the last generic is the runtime when using this macro.
94#[doc(hidden)]
95#[proc_macro_attribute]
96pub fn default_runtime(attributes: TokenStream, input: TokenStream) -> TokenStream {
97  let attributes = parse_macro_input!(attributes as runtime::Attributes);
98  let input = parse_macro_input!(input as runtime::Input);
99  runtime::default_runtime(attributes, input).into()
100}
101
102/// Accepts a closure-like syntax to call arbitrary code on a menu item
103/// after matching against `kind` and retrieving it from `resources_table` using `rid`.
104///
105/// You can optionally pass a 5th parameter to select which item kinds
106/// to match against, by providing a `|` separated list of item kinds
107/// ```ignore
108/// do_menu_item!(resources_table, rid, kind, |i| i.set_text(text), Check | Submenu);
109/// ```
110/// You could also provide a negated list
111/// ```ignore
112/// do_menu_item!(resources_table, rid, kind, |i| i.set_text(text), !Check);
113/// do_menu_item!(resources_table, rid, kind, |i| i.set_text(text), !Check | !Submenu);
114/// ```
115/// but you can't have mixed negations and positive kinds.
116/// ```ignore
117/// do_menu_item!(resources_table, rid, kind, |i| i.set_text(text), !Check | Submenu);
118/// ```
119///
120/// #### Example
121///
122/// ```ignore
123///  let rid = 23;
124///  let kind = ItemKind::Check;
125///  let resources_table = app.resources_table();
126///  do_menu_item!(resources_table, rid, kind, |i| i.set_text(text))
127/// ```
128/// which will expand into:
129/// ```ignore
130///  let rid = 23;
131///  let kind = ItemKind::Check;
132///  let resources_table = app.resources_table();
133///  match kind {
134///  ItemKind::Submenu => {
135///    let i = resources_table.get::<Submenu<R>>(rid)?;
136///    i.set_text(text)
137///  }
138///  ItemKind::MenuItem => {
139///    let i = resources_table.get::<MenuItem<R>>(rid)?;
140///    i.set_text(text)
141///  }
142///  ItemKind::Predefined => {
143///    let i = resources_table.get::<PredefinedMenuItem<R>>(rid)?;
144///    i.set_text(text)
145///  }
146///  ItemKind::Check => {
147///    let i = resources_table.get::<CheckMenuItem<R>>(rid)?;
148///    i.set_text(text)
149///  }
150///  ItemKind::Icon => {
151///    let i = resources_table.get::<IconMenuItem<R>>(rid)?;
152///    i.set_text(text)
153///  }
154///  _ => unreachable!(),
155///  }
156/// ```
157#[proc_macro]
158pub fn do_menu_item(input: TokenStream) -> TokenStream {
159  let tokens = parse_macro_input!(input as menu::DoMenuItemInput);
160  menu::do_menu_item(tokens).into()
161}
162
163/// Convert a .png or .ico icon to an Image
164/// for things like `tauri::tray::TrayIconBuilder` to consume,
165/// relative paths are resolved from `CARGO_MANIFEST_DIR`, not current file
166///
167/// ### Examples
168///
169/// ```ignore
170/// const APP_ICON: Image<'_> = include_image!("./icons/32x32.png");
171///
172/// // then use it with tray
173/// TrayIconBuilder::new().icon(APP_ICON).build().unwrap();
174///
175/// // or with window
176/// WebviewWindowBuilder::new(app, "main", WebviewUrl::default())
177///     .icon(APP_ICON)
178///     .unwrap()
179///     .build()
180///     .unwrap();
181///
182/// // or with any other functions that takes `Image` struct
183/// ```
184///
185/// Note: this stores the image in raw pixels to the final binary,
186/// so keep the icon size (width and height) small
187/// or else it's going to bloat your final executable
188#[proc_macro]
189pub fn include_image(tokens: TokenStream) -> TokenStream {
190  let path = match parse2::<LitStr>(tokens.into()) {
191    Ok(path) => path,
192    Err(err) => return err.into_compile_error().into(),
193  };
194  let path = PathBuf::from(path.value());
195  let resolved_path = if path.is_relative() {
196    if let Ok(base_dir) = std::env::var("CARGO_MANIFEST_DIR").map(PathBuf::from) {
197      base_dir.join(path)
198    } else {
199      return quote!(compile_error!("$CARGO_MANIFEST_DIR is not defined")).into();
200    }
201  } else {
202    path
203  };
204  if !resolved_path.exists() {
205    let error_string = format!(
206      "Provided Image path \"{}\" doesn't exists",
207      resolved_path.display()
208    );
209    return quote!(compile_error!(#error_string)).into();
210  }
211
212  match CachedIcon::new(&quote!(::tauri), &resolved_path).map_err(|error| error.to_string()) {
213    Ok(icon) => icon.into_token_stream(),
214    Err(error) => quote!(compile_error!(#error)),
215  }
216  .into()
217}