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//! Don't depend on this crate directly, use the re-exported types from tauri instead.
8
9#![doc(
10 html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png",
11 html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png"
12)]
13
14use std::path::PathBuf;
15
16use crate::context::ContextItems;
17use proc_macro::TokenStream;
18use quote::{ToTokens, quote};
19use syn::{LitStr, parse_macro_input, parse2};
20use tauri_codegen::image::CachedIcon;
21
22mod cef;
23mod command;
24mod menu;
25mod mobile;
26
27#[macro_use]
28mod context;
29
30/// Mark a function as a command handler. It creates a wrapper function with the necessary glue code.
31///
32/// The wrapped function can then be passed to [`generate_handler!`] so it can be called
33/// from the frontend with `invoke()`.
34///
35/// ```rust,ignore
36/// #[tauri::command]
37/// fn greet(name: String) -> String {
38/// format!("Hello, {name}!")
39/// }
40/// ```
41///
42/// # Options
43///
44/// The attribute accepts a comma separated list of the following options:
45///
46/// ## `async`
47///
48/// Runs the command on the async runtime instead of blocking the main thread.
49///
50/// `async fn` commands are always executed asynchronously, so this option is only needed
51/// for synchronous functions that should not run on the main thread:
52///
53/// ```rust,ignore
54/// #[tauri::command(async)]
55/// fn expensive_computation() -> u64 {
56/// // the body runs on the async runtime, so the main thread is not blocked
57/// 42
58/// }
59/// ```
60///
61/// ## `rename_all`
62///
63/// Sets the case convention used to match the command arguments with the keys of the
64/// payload sent by the frontend. Either `"camelCase"` (default) or `"snake_case"`.
65///
66/// ```rust,ignore
67/// // called from JavaScript with `invoke("send_message", { messageBody: "Hello" })`
68/// #[tauri::command]
69/// fn send_message(message_body: String) {}
70///
71/// // called from JavaScript with `invoke("send_message", { message_body: "Hello" })`
72/// #[tauri::command(rename_all = "snake_case")]
73/// fn send_message_snake(message_body: String) {}
74/// ```
75///
76/// ## `rename`
77///
78/// Changes the name used to call the command from the frontend.
79/// By default it is the name of the function.
80///
81/// ```rust,ignore
82/// // called from JavaScript with `invoke("greetUser")`
83/// // and still registered as `generate_handler![greet_user]`
84/// #[tauri::command(rename = "greetUser")]
85/// fn greet_user() {}
86/// ```
87///
88/// ## `root`
89///
90/// Path to the `tauri` crate, used when it is renamed in `Cargo.toml` or re-exported
91/// by another crate. Defaults to `::tauri`, and the special value `"crate"` resolves
92/// to `$crate` (used internally by Tauri itself).
93///
94/// ```rust,ignore
95/// // Cargo.toml: tauri_framework = { package = "tauri", version = "2" }
96/// #[tauri::command(root = "tauri_framework")]
97/// fn my_command() {}
98/// ```
99///
100/// # Inline plugins
101///
102/// When the command belongs to a plugin that is part of your application instead of a
103/// standalone crate, annotate it in the [`generate_handler!`] list with the
104/// `#![plugin(your_plugin_name)]` inner attribute so `build > removeUnusedCommands` can
105/// match it against the plugin permissions.
106///
107/// # Stability
108/// The output of this macro is managed internally by Tauri,
109/// and should not be accessed directly on normal applications.
110/// It may have breaking changes in the future.
111#[proc_macro_attribute]
112pub fn command(attributes: TokenStream, item: TokenStream) -> TokenStream {
113 command::wrapper(attributes, item)
114}
115
116/// Marks a function as the entry point of a mobile application.
117///
118/// It must be applied to the function that builds and runs your Tauri application on the
119/// library target (`run()` on `src-tauri/src/lib.rs` for apps created by the Tauri CLI),
120/// which is the function the generated Android and iOS projects call on startup.
121///
122/// The canonical usage only applies it on mobile targets, so the same function can be called
123/// by the `main.rs` of the desktop binary:
124///
125/// ```rust,ignore
126/// #[cfg_attr(mobile, tauri::mobile_entry_point)]
127/// pub fn run() {
128/// tauri::Builder::default()
129/// .run(tauri::generate_context!())
130/// .expect("error while running tauri application");
131/// }
132/// ```
133///
134/// The macro generates a `start_app` C symbol (checked by the Tauri CLI) that catches
135/// panics instead of unwinding across the FFI boundary, blocks on the function when it is
136/// `async`, sets up the stdout logger on iOS and the JNI bindings on Android using the
137/// package name derived from the `identifier` in your Tauri configuration.
138///
139/// Because the Android package name is read from environment variables set by `tauri-build`,
140/// your application must have a build script calling [`tauri_build::build`] - otherwise the
141/// macro fails to compile with a `env var not set` error.
142///
143/// [`tauri_build::build`]: https://docs.rs/tauri-build/latest/tauri_build/fn.build.html
144#[proc_macro_attribute]
145pub fn mobile_entry_point(attributes: TokenStream, item: TokenStream) -> TokenStream {
146 mobile::entry_point(attributes, item)
147}
148
149/// Mark a function as the CEF entry point. It creates a main function that only runs
150/// the provided function for the browser process, and runs the CEF helper process
151/// for other processes (renderer, GPU, plugin, etc.).
152///
153/// # Examples
154///
155/// ```rust,ignore
156/// use tauri_macros::cef_entry_point;
157///
158/// #[cef_entry_point]
159/// fn main() {
160/// // Your app initialization code here
161/// // This will only run for the browser process
162/// }
163/// ```
164///
165/// # Stability
166/// The output of this macro is managed internally by Tauri,
167/// and should not be accessed directly on normal applications.
168/// It may have breaking changes in the future.
169#[proc_macro_attribute]
170pub fn cef_entry_point(attributes: TokenStream, item: TokenStream) -> TokenStream {
171 cef::entry_point(attributes, item)
172}
173
174/// Accepts a list of command functions. Creates a handler that allows commands to be called from JS with invoke().
175///
176/// You can optionally annotate the commands with a inner attribute tag `#![plugin(your_plugin_name)]`
177/// for `build > removeUnusedCommands` to work for plugins not defined in a standalone crate like `tauri-plugin-fs`
178///
179/// # Examples
180///
181/// ```rust,ignore
182/// use tauri_macros::{command, generate_handler};
183/// #[command]
184/// fn command_one() {
185/// println!("command one called");
186/// }
187/// #[command]
188/// fn command_two() {
189/// println!("command two called");
190/// }
191/// fn main() {
192/// let _handler = generate_handler![command_one, command_two];
193/// }
194/// ```
195///
196/// # Stability
197///
198/// The output of this macro is managed internally by Tauri,
199/// and should not be accessed directly on normal applications.
200/// It may have breaking changes in the future.
201#[proc_macro]
202pub fn generate_handler(item: TokenStream) -> TokenStream {
203 parse_macro_input!(item as command::Handler).into()
204}
205
206/// Reads a Tauri config file and generates a `::tauri::Context` based on the content.
207///
208/// The context embeds the frontend assets, the application icons, the resolved Access Control List
209/// and the parsed configuration into the binary, and is passed to
210/// `tauri::Builder::run`/`tauri::Builder::build`.
211///
212/// ```rust,ignore
213/// tauri::Builder::default()
214/// .run(tauri::generate_context!())
215/// .expect("error while running tauri application");
216/// ```
217///
218/// # Options
219///
220/// All options are optional and can be combined in a comma separated list.
221///
222/// ## Configuration file path
223///
224/// A string literal as the first argument sets the path of the Tauri configuration file to read,
225/// relative to `CARGO_MANIFEST_DIR`. Defaults to `tauri.conf.json` on the crate directory.
226/// Platform specific configuration files (e.g. `tauri.windows.conf.json`) that sit next to it are
227/// merged as usual.
228///
229/// ```rust,ignore
230/// tauri::generate_context!("../tauri.conf.json");
231/// ```
232///
233/// ## Root path
234///
235/// A path (any item that is not a `key = value` pair) changes the crate path the generated code
236/// refers to. Defaults to `::tauri`, and is only needed when the `tauri` crate is renamed,
237/// re-exported by another crate, or is the crate being compiled (`crate`).
238///
239/// ```rust,ignore
240/// tauri::generate_context!("../tauri.conf.json", ::my_framework::tauri);
241/// ```
242///
243/// ## `capabilities`
244///
245/// A list of additional capability files to include in the generated Access Control List,
246/// on top of the ones defined in the `capabilities` directory and in the
247/// `app > security > capabilities` configuration value.
248///
249/// Each item is a path (relative to the current working directory of the compiler,
250/// usually the crate directory) to a JSON or TOML file containing a capability,
251/// a list of capabilities or a named list of capabilities.
252///
253/// ```rust,ignore
254/// tauri::generate_context!(capabilities = ["./capabilities/extra.json"]);
255/// ```
256///
257/// ## `assets`
258///
259/// An expression resolving to a custom [`tauri::Assets`] implementation, used instead of
260/// embedding the files from `build > frontendDist`. Useful for serving the frontend from a
261/// custom source, or for skipping asset embedding on tests.
262///
263/// ```rust,ignore
264/// tauri::generate_context!(assets = tauri::test::noop_assets());
265/// ```
266///
267/// ## `test`
268///
269/// When `true`, skips code generation that misbehaves when the context is created inside a
270/// test binary - currently the `Info.plist` embedding performed on macOS development builds.
271/// Defaults to `false`.
272///
273/// ```rust,ignore
274/// let context = tauri::generate_context!("../tauri.conf.json", test = true);
275/// ```
276///
277/// [`tauri::Assets`]: https://docs.rs/tauri/latest/tauri/trait.Assets.html
278///
279/// # Stability
280/// The output of this macro is managed internally by Tauri,
281/// and should not be accessed directly on normal applications.
282/// It may have breaking changes in the future.
283#[proc_macro]
284pub fn generate_context(items: TokenStream) -> TokenStream {
285 // this macro is exported from the context module
286 let path = parse_macro_input!(items as ContextItems);
287 context::generate_context(path).into()
288}
289
290/// Accepts a closure-like syntax to call arbitrary code on a menu item
291/// after matching against `kind` and retrieving it from `resources_table` using `rid`.
292///
293/// You can optionally pass a 5th parameter to select which item kinds
294/// to match against, by providing a `|` separated list of item kinds
295/// ```ignore
296/// do_menu_item!(resources_table, rid, kind, |i| i.set_text(text), Check | Submenu);
297/// ```
298/// You could also provide a negated list
299/// ```ignore
300/// do_menu_item!(resources_table, rid, kind, |i| i.set_text(text), !Check);
301/// do_menu_item!(resources_table, rid, kind, |i| i.set_text(text), !Check | !Submenu);
302/// ```
303/// but you can't have mixed negations and positive kinds.
304/// ```ignore
305/// do_menu_item!(resources_table, rid, kind, |i| i.set_text(text), !Check | Submenu);
306/// ```
307///
308/// ## Examples
309///
310/// ```ignore
311/// let rid = 23;
312/// let kind = ItemKind::Check;
313/// let resources_table = app.resources_table();
314/// do_menu_item!(resources_table, rid, kind, |i| i.set_text(text))
315/// ```
316/// which will expand into:
317/// ```ignore
318/// let rid = 23;
319/// let kind = ItemKind::Check;
320/// let resources_table = app.resources_table();
321/// match kind {
322/// ItemKind::Submenu => {
323/// let i = resources_table.get::<Submenu<R>>(rid)?;
324/// i.set_text(text)
325/// }
326/// ItemKind::MenuItem => {
327/// let i = resources_table.get::<MenuItem<R>>(rid)?;
328/// i.set_text(text)
329/// }
330/// ItemKind::Predefined => {
331/// let i = resources_table.get::<PredefinedMenuItem<R>>(rid)?;
332/// i.set_text(text)
333/// }
334/// ItemKind::Check => {
335/// let i = resources_table.get::<CheckMenuItem<R>>(rid)?;
336/// i.set_text(text)
337/// }
338/// ItemKind::Icon => {
339/// let i = resources_table.get::<IconMenuItem<R>>(rid)?;
340/// i.set_text(text)
341/// }
342/// _ => return Err(crate::Error::UnexpectedMenuKind),
343/// }
344/// ```
345#[doc(hidden)]
346#[proc_macro]
347pub fn do_menu_item(input: TokenStream) -> TokenStream {
348 let tokens = parse_macro_input!(input as menu::DoMenuItemInput);
349 menu::do_menu_item(tokens).into()
350}
351
352/// Convert a .png or .ico icon to an Image
353/// for things like `tauri::tray::TrayIconBuilder` to consume,
354/// relative paths are resolved from `CARGO_MANIFEST_DIR`, not current file
355///
356/// ### Examples
357///
358/// ```ignore
359/// const APP_ICON: Image<'_> = include_image!("./icons/32x32.png");
360///
361/// // then use it with tray
362/// TrayIconBuilder::new().icon(APP_ICON).build().unwrap();
363///
364/// // or with window
365/// WebviewWindowBuilder::new(app, "main", WebviewUrl::default())
366/// .icon(APP_ICON)
367/// .unwrap()
368/// .build()
369/// .unwrap();
370///
371/// // or with any other functions that takes `Image` struct
372/// ```
373///
374/// Note: this stores the image in raw pixels to the final binary,
375/// so keep the icon size (width and height) small
376/// or else it's going to bloat your final executable
377#[proc_macro]
378pub fn include_image(tokens: TokenStream) -> TokenStream {
379 let path = match parse2::<LitStr>(tokens.into()) {
380 Ok(path) => path,
381 Err(err) => return err.into_compile_error().into(),
382 };
383 let path = PathBuf::from(path.value());
384 let resolved_path = if path.is_relative() {
385 if let Ok(base_dir) = std::env::var("CARGO_MANIFEST_DIR").map(PathBuf::from) {
386 base_dir.join(path)
387 } else {
388 return quote!(compile_error!("$CARGO_MANIFEST_DIR is not defined")).into();
389 }
390 } else {
391 path
392 };
393 if !resolved_path.exists() {
394 let error_string = format!(
395 "Provided Image path \"{}\" doesn't exists",
396 resolved_path.display()
397 );
398 return quote!(compile_error!(#error_string)).into();
399 }
400
401 match CachedIcon::new("e!(::tauri), &resolved_path).map_err(|error| error.to_string()) {
402 Ok(icon) => icon.into_token_stream(),
403 Err(error) => quote!(compile_error!(#error)),
404 }
405 .into()
406}