Skip to main content

tauri_utils/
config.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//! The Tauri configuration used at runtime.
6//!
7//! It is pulled from a `tauri.conf.json` file and the [`Config`] struct is generated at compile time.
8//!
9//! # Stability
10//!
11//! This is a core functionality that is not considered part of the stable API.
12//! If you use it, note that it may include breaking changes in the future.
13//!
14//! These items are intended to be non-breaking from a de/serialization standpoint only.
15//! Using and modifying existing config values will try to avoid breaking changes, but they are
16//! free to add fields in the future - causing breaking changes for creating and full destructuring.
17//!
18//! To avoid this, [ignore unknown fields when destructuring] with the `{my, config, ..}` pattern.
19//! If you need to create the Rust config directly without deserializing, then create the struct
20//! the [Struct Update Syntax] with `..Default::default()`, which may need a
21//! `#[allow(clippy::needless_update)]` attribute if you are declaring all fields.
22//!
23//! [ignore unknown fields when destructuring]: https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html#ignoring-remaining-parts-of-a-value-with-
24//! [Struct Update Syntax]: https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-from-other-instances-with-struct-update-syntax
25
26#[cfg(feature = "schema")]
27use schemars::JsonSchema;
28use semver::Version;
29use serde::{
30  Deserialize, Serialize, Serializer,
31  de::{Deserializer, Error as DeError, Visitor},
32};
33use serde_json::Value as JsonValue;
34use serde_untagged::UntaggedEnumVisitor;
35use serde_with::skip_serializing_none;
36use url::Url;
37
38use std::{
39  collections::{BTreeMap, HashMap, HashSet},
40  fmt::{self, Display},
41  fs::read_to_string,
42  path::{Component, Path, PathBuf},
43  str::FromStr,
44};
45
46/// Items to help with parsing content into a [`Config`].
47pub mod parse;
48
49use crate::{TitleBarStyle, WindowEffect, WindowEffectState, acl::capability::Capability};
50
51pub use self::parse::parse;
52
53fn default_true() -> bool {
54  true
55}
56
57/// An URL to open on a Tauri webview window.
58#[derive(PartialEq, Eq, Debug, Clone, Serialize)]
59#[cfg_attr(feature = "schema", derive(JsonSchema))]
60#[serde(untagged)]
61#[non_exhaustive]
62pub enum WebviewUrl {
63  /// An external URL. Must use either the `http` or `https` schemes.
64  External(Url),
65  /// The path portion of an app URL.
66  /// For instance, to load `tauri://localhost/users/john`,
67  /// you can simply provide `users/john` in this configuration.
68  App(PathBuf),
69  /// A custom protocol url, for example, `doom://index.html`
70  CustomProtocol(Url),
71}
72
73impl<'de> Deserialize<'de> for WebviewUrl {
74  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
75  where
76    D: Deserializer<'de>,
77  {
78    #[derive(Deserialize)]
79    #[serde(untagged)]
80    enum WebviewUrlDeserializer {
81      Url(Url),
82      Path(PathBuf),
83    }
84
85    match WebviewUrlDeserializer::deserialize(deserializer)? {
86      WebviewUrlDeserializer::Url(u) => {
87        if u.scheme() == "https" || u.scheme() == "http" {
88          Ok(Self::External(u))
89        } else {
90          Ok(Self::CustomProtocol(u))
91        }
92      }
93      WebviewUrlDeserializer::Path(p) => Ok(Self::App(p)),
94    }
95  }
96}
97
98impl fmt::Display for WebviewUrl {
99  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100    match self {
101      Self::External(url) | Self::CustomProtocol(url) => write!(f, "{url}"),
102      Self::App(path) => write!(f, "{}", path.display()),
103    }
104  }
105}
106
107impl Default for WebviewUrl {
108  fn default() -> Self {
109    Self::App("index.html".into())
110  }
111}
112
113/// A bundle referenced by tauri-bundler.
114#[derive(Debug, PartialEq, Eq, Clone)]
115#[cfg_attr(feature = "schema", derive(JsonSchema))]
116#[cfg_attr(feature = "schema", schemars(rename_all = "lowercase"))]
117pub enum BundleType {
118  /// The debian bundle (.deb).
119  Deb,
120  /// The RPM bundle (.rpm).
121  Rpm,
122  /// The AppImage bundle (.appimage).
123  AppImage,
124  /// The Microsoft Installer bundle (.msi).
125  Msi,
126  /// The NSIS bundle (.exe).
127  Nsis,
128  /// The macOS application bundle (.app).
129  App,
130  /// The Apple Disk Image bundle (.dmg).
131  Dmg,
132}
133
134impl BundleType {
135  /// All bundle types.
136  fn all() -> &'static [Self] {
137    &[
138      BundleType::Deb,
139      BundleType::Rpm,
140      BundleType::AppImage,
141      BundleType::Msi,
142      BundleType::Nsis,
143      BundleType::App,
144      BundleType::Dmg,
145    ]
146  }
147}
148
149impl Display for BundleType {
150  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151    write!(
152      f,
153      "{}",
154      match self {
155        Self::Deb => "deb",
156        Self::Rpm => "rpm",
157        Self::AppImage => "appimage",
158        Self::Msi => "msi",
159        Self::Nsis => "nsis",
160        Self::App => "app",
161        Self::Dmg => "dmg",
162      }
163    )
164  }
165}
166
167impl Serialize for BundleType {
168  fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
169  where
170    S: Serializer,
171  {
172    serializer.serialize_str(self.to_string().as_ref())
173  }
174}
175
176impl<'de> Deserialize<'de> for BundleType {
177  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
178  where
179    D: Deserializer<'de>,
180  {
181    let s = String::deserialize(deserializer)?;
182    match s.to_lowercase().as_str() {
183      "deb" => Ok(Self::Deb),
184      "rpm" => Ok(Self::Rpm),
185      "appimage" => Ok(Self::AppImage),
186      "msi" => Ok(Self::Msi),
187      "nsis" => Ok(Self::Nsis),
188      "app" => Ok(Self::App),
189      "dmg" => Ok(Self::Dmg),
190      _ => Err(DeError::custom(format!("unknown bundle target '{s}'"))),
191    }
192  }
193}
194
195/// Targets to bundle. Each value is case insensitive.
196#[derive(Debug, PartialEq, Eq, Clone, Default)]
197#[cfg_attr(
198  feature = "schema",
199  derive(JsonSchema),
200  schemars(rename_all = "lowercase")
201)]
202pub enum BundleTarget {
203  /// Bundle all targets.
204  #[default]
205  All,
206  #[cfg_attr(feature = "schema", schemars(untagged))]
207  /// A list of bundle targets.
208  List(Vec<BundleType>),
209  #[cfg_attr(feature = "schema", schemars(untagged))]
210  /// A single bundle target.
211  One(BundleType),
212}
213
214impl Serialize for BundleTarget {
215  fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
216  where
217    S: Serializer,
218  {
219    match self {
220      Self::All => serializer.serialize_str("all"),
221      Self::List(l) => l.serialize(serializer),
222      Self::One(t) => serializer.serialize_str(t.to_string().as_ref()),
223    }
224  }
225}
226
227impl<'de> Deserialize<'de> for BundleTarget {
228  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
229  where
230    D: Deserializer<'de>,
231  {
232    #[derive(Deserialize, Serialize)]
233    #[serde(untagged)]
234    pub enum BundleTargetInner {
235      List(Vec<BundleType>),
236      One(BundleType),
237      All(String),
238    }
239
240    match BundleTargetInner::deserialize(deserializer)? {
241      BundleTargetInner::All(s) if s.to_lowercase() == "all" => Ok(Self::All),
242      BundleTargetInner::All(t) => Err(DeError::custom(format!(
243        "invalid bundle type {t}, expected one of `all`, {}",
244        BundleType::all()
245          .iter()
246          .map(|b| format!("`{b}`"))
247          .collect::<Vec<_>>()
248          .join(", ")
249      ))),
250      BundleTargetInner::List(l) => Ok(Self::List(l)),
251      BundleTargetInner::One(t) => Ok(Self::One(t)),
252    }
253  }
254}
255
256impl BundleTarget {
257  /// Gets the bundle targets as a [`Vec`]. The vector is empty when set to [`BundleTarget::All`].
258  #[allow(dead_code)]
259  pub fn to_vec(&self) -> Vec<BundleType> {
260    match self {
261      Self::All => BundleType::all().to_vec(),
262      Self::List(list) => list.clone(),
263      Self::One(i) => vec![i.clone()],
264    }
265  }
266}
267
268/// Configuration for AppImage bundles.
269///
270/// See more: <https://v2.tauri.app/reference/config/#appimageconfig>
271#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
272#[cfg_attr(feature = "schema", derive(JsonSchema))]
273#[serde(rename_all = "camelCase", deny_unknown_fields)]
274pub struct AppImageConfig {
275  /// Include additional gstreamer dependencies needed for audio and video playback.
276  /// This increases the bundle size by ~15-35MB depending on your build system.
277  #[serde(default, alias = "bundle-media-framework")]
278  pub bundle_media_framework: bool,
279  /// The files to include in the Appimage Binary.
280  #[serde(default)]
281  pub files: HashMap<PathBuf, PathBuf>,
282}
283
284const ZSTD_MIN_CLEVEL: i32 = -1 << 17;
285
286/// Compression algorithms used when bundling DEB packages.
287#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
288#[cfg_attr(feature = "schema", derive(JsonSchema))]
289#[serde(rename_all = "camelCase", deny_unknown_fields, tag = "type")]
290#[non_exhaustive]
291pub enum DebCompression {
292  /// Gzip compression
293  Gzip {
294    /// Gzip compression level (0-9).
295    level: u32,
296  },
297  /// Zstd compression
298  Zstd {
299    /// Zstd compression level (-131072 - 22).
300    level: i32,
301  },
302  /// Xz compression
303  Xz {
304    /// Xz compression level (0-9).
305    level: u32,
306  },
307  /// Bzip2 compression
308  Bzip2 {
309    /// Bzip2 compression level (1-9).
310    level: u32,
311  },
312  /// Disable compression
313  None,
314}
315
316impl DebCompression {
317  /// The file extension of a tar archive compressed with this algorithm.
318  pub fn ext(&self) -> &'static str {
319    match self {
320      Self::None => "tar",
321      Self::Gzip { .. } => "tar.gz",
322      Self::Xz { .. } => "tar.xz",
323      Self::Zstd { .. } => "tar.zst",
324      Self::Bzip2 { .. } => "tar.bz2",
325    }
326  }
327
328  /// The default compression level for this algorithm.
329  pub fn default_level(&self) -> Option<i32> {
330    match self {
331      Self::None => None,
332      Self::Gzip { .. } => Some(9),
333      Self::Xz { .. } => Some(9),
334      Self::Zstd { .. } => Some(19),
335      Self::Bzip2 { .. } => Some(9),
336    }
337  }
338
339  /// The minimum compression level supported by this algorithm.
340  pub fn min_level(&self) -> Option<i32> {
341    match self {
342      Self::None => None,
343      Self::Gzip { .. } => Some(0),
344      Self::Xz { .. } => Some(0),
345      Self::Zstd { .. } => Some(ZSTD_MIN_CLEVEL),
346      // bzip2 levels are block sizes, which start at 1
347      Self::Bzip2 { .. } => Some(1),
348    }
349  }
350
351  /// The maximum compression level supported by this algorithm.
352  pub fn max_level(&self) -> Option<i32> {
353    match self {
354      Self::None => None,
355      Self::Gzip { .. } => Some(9),
356      Self::Xz { .. } => Some(9),
357      Self::Zstd { .. } => Some(22),
358      Self::Bzip2 { .. } => Some(9),
359    }
360  }
361
362  /// The compression level configured for this algorithm.
363  pub fn level(&self) -> Option<i32> {
364    match self {
365      Self::None => None,
366      Self::Gzip { level } | Self::Xz { level } | Self::Bzip2 { level } => {
367        Some(i32::try_from(*level).unwrap_or(i32::MAX))
368      }
369      Self::Zstd { level } => Some(*level),
370    }
371  }
372}
373
374/// Configuration for Debian (.deb) bundles.
375///
376/// See more: <https://v2.tauri.app/reference/config/#debconfig>
377#[skip_serializing_none]
378#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
379#[cfg_attr(feature = "schema", derive(JsonSchema))]
380#[serde(rename_all = "camelCase", deny_unknown_fields)]
381pub struct DebConfig {
382  /// The list of deb dependencies your application relies on.
383  pub depends: Option<Vec<String>>,
384  /// The list of deb dependencies your application recommends.
385  pub recommends: Option<Vec<String>>,
386  /// The list of dependencies the package provides.
387  pub provides: Option<Vec<String>>,
388  /// The list of package conflicts.
389  pub conflicts: Option<Vec<String>>,
390  /// The list of package replaces.
391  pub replaces: Option<Vec<String>>,
392  /// The files to include on the package.
393  #[serde(default)]
394  pub files: HashMap<PathBuf, PathBuf>,
395  /// Define the section in Debian Control file. See : https://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections
396  pub section: Option<String>,
397  /// Change the priority of the Debian Package. By default, it is set to `optional`.
398  /// Recognized Priorities as of now are :  `required`, `important`, `standard`, `optional`, `extra`
399  pub priority: Option<String>,
400  /// Path of the uncompressed Changelog file, to be stored at /usr/share/doc/package-name/changelog.gz. See
401  /// <https://www.debian.org/doc/debian-policy/ch-docs.html#changelog-files-and-release-notes>
402  pub changelog: Option<PathBuf>,
403  /// Path to a custom desktop file Handlebars template.
404  ///
405  /// Available variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.
406  #[serde(alias = "desktop-template")]
407  pub desktop_template: Option<PathBuf>,
408  /// Path to script that will be executed before the package is unpacked. See
409  /// <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>
410  #[serde(alias = "pre-install-script")]
411  pub pre_install_script: Option<PathBuf>,
412  /// Path to script that will be executed after the package is unpacked. See
413  /// <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>
414  #[serde(alias = "post-install-script")]
415  pub post_install_script: Option<PathBuf>,
416  /// Path to script that will be executed before the package is removed. See
417  /// <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>
418  #[serde(alias = "pre-remove-script")]
419  pub pre_remove_script: Option<PathBuf>,
420  /// Path to script that will be executed after the package is removed. See
421  /// <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>
422  #[serde(alias = "post-remove-script")]
423  pub post_remove_script: Option<PathBuf>,
424  /// Compression algorithm and level. Defaults to `Gzip` with level 6.
425  pub compression: Option<DebCompression>,
426}
427
428/// Configuration for Linux bundles.
429///
430/// See more: <https://v2.tauri.app/reference/config/#linuxconfig>
431#[skip_serializing_none]
432#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
433#[cfg_attr(feature = "schema", derive(JsonSchema))]
434#[serde(rename_all = "camelCase", deny_unknown_fields)]
435pub struct LinuxConfig {
436  /// Configuration for the AppImage bundle.
437  #[serde(default)]
438  pub appimage: AppImageConfig,
439  /// Configuration for the Debian bundle.
440  #[serde(default)]
441  pub deb: DebConfig,
442  /// Configuration for the RPM bundle.
443  #[serde(default)]
444  pub rpm: RpmConfig,
445}
446
447/// Compression algorithms used when bundling RPM packages.
448#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
449#[cfg_attr(feature = "schema", derive(JsonSchema))]
450#[serde(rename_all = "camelCase", deny_unknown_fields, tag = "type")]
451#[non_exhaustive]
452pub enum RpmCompression {
453  /// Gzip compression
454  Gzip {
455    /// Gzip compression level (0-9).
456    level: u32,
457  },
458  /// Zstd compression
459  Zstd {
460    /// Zstd compression level (-131072 - 22).
461    level: i32,
462  },
463  /// Xz compression
464  Xz {
465    /// Xz compression level (0-9).
466    level: u32,
467  },
468  /// Bzip2 compression
469  Bzip2 {
470    /// Bzip2 compression level (1-9).
471    level: u32,
472  },
473  /// Disable compression
474  None,
475}
476
477impl RpmCompression {
478  /// The default compression level for this algorithm.
479  pub fn default_level(&self) -> Option<i32> {
480    match self {
481      Self::None => None,
482      Self::Gzip { .. } => Some(9),
483      Self::Xz { .. } => Some(9),
484      Self::Zstd { .. } => Some(19),
485      Self::Bzip2 { .. } => Some(9),
486    }
487  }
488
489  /// The minimum compression level supported by this algorithm.
490  pub fn min_level(&self) -> Option<i32> {
491    match self {
492      Self::None => None,
493      Self::Gzip { .. } => Some(0),
494      Self::Xz { .. } => Some(0),
495      Self::Zstd { .. } => Some(ZSTD_MIN_CLEVEL),
496      // bzip2 levels are block sizes, which start at 1
497      Self::Bzip2 { .. } => Some(1),
498    }
499  }
500
501  /// The maximum compression level supported by this algorithm.
502  pub fn max_level(&self) -> Option<i32> {
503    match self {
504      Self::None => None,
505      Self::Gzip { .. } => Some(9),
506      Self::Xz { .. } => Some(9),
507      Self::Zstd { .. } => Some(22),
508      Self::Bzip2 { .. } => Some(9),
509    }
510  }
511
512  /// The compression level configured for this algorithm.
513  pub fn level(&self) -> Option<i32> {
514    match self {
515      Self::None => None,
516      Self::Gzip { level } | Self::Xz { level } | Self::Bzip2 { level } => {
517        Some(i32::try_from(*level).unwrap_or(i32::MAX))
518      }
519      Self::Zstd { level } => Some(*level),
520    }
521  }
522}
523
524/// Configuration for RPM bundles.
525#[skip_serializing_none]
526#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
527#[cfg_attr(feature = "schema", derive(JsonSchema))]
528#[serde(rename_all = "camelCase", deny_unknown_fields)]
529pub struct RpmConfig {
530  /// The list of RPM dependencies your application relies on.
531  pub depends: Option<Vec<String>>,
532  /// The list of RPM dependencies your application recommends.
533  pub recommends: Option<Vec<String>>,
534  /// The list of RPM dependencies your application provides.
535  pub provides: Option<Vec<String>>,
536  /// The list of RPM dependencies your application conflicts with. They must not be present
537  /// in order for the package to be installed.
538  pub conflicts: Option<Vec<String>>,
539  /// The list of RPM dependencies your application supersedes - if this package is installed,
540  /// packages listed as "obsoletes" will be automatically removed (if they are present).
541  pub obsoletes: Option<Vec<String>>,
542  /// The RPM release tag.
543  #[serde(default = "default_release")]
544  pub release: String,
545  /// The RPM epoch.
546  #[serde(default)]
547  pub epoch: u32,
548  /// The files to include on the package.
549  #[serde(default)]
550  pub files: HashMap<PathBuf, PathBuf>,
551  /// Path to a custom desktop file Handlebars template.
552  ///
553  /// Available variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.
554  #[serde(alias = "desktop-template")]
555  pub desktop_template: Option<PathBuf>,
556  /// Path to script that will be executed before the package is unpacked. See
557  /// <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>
558  #[serde(alias = "pre-install-script")]
559  pub pre_install_script: Option<PathBuf>,
560  /// Path to script that will be executed after the package is unpacked. See
561  /// <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>
562  #[serde(alias = "post-install-script")]
563  pub post_install_script: Option<PathBuf>,
564  /// Path to script that will be executed before the package is removed. See
565  /// <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>
566  #[serde(alias = "pre-remove-script")]
567  pub pre_remove_script: Option<PathBuf>,
568  /// Path to script that will be executed after the package is removed. See
569  /// <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>
570  #[serde(alias = "post-remove-script")]
571  pub post_remove_script: Option<PathBuf>,
572  /// Compression algorithm and level. Defaults to `Gzip` with level 6.
573  pub compression: Option<RpmCompression>,
574}
575
576impl Default for RpmConfig {
577  fn default() -> Self {
578    Self {
579      depends: None,
580      recommends: None,
581      provides: None,
582      conflicts: None,
583      obsoletes: None,
584      release: default_release(),
585      epoch: 0,
586      files: Default::default(),
587      desktop_template: None,
588      pre_install_script: None,
589      post_install_script: None,
590      pre_remove_script: None,
591      post_remove_script: None,
592      compression: None,
593    }
594  }
595}
596
597fn default_release() -> String {
598  "1".into()
599}
600
601/// Position coordinates struct.
602#[derive(Default, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
603#[cfg_attr(feature = "schema", derive(JsonSchema))]
604#[serde(rename_all = "camelCase", deny_unknown_fields)]
605pub struct Position {
606  /// X coordinate.
607  pub x: u32,
608  /// Y coordinate.
609  pub y: u32,
610}
611
612/// Position coordinates struct.
613#[derive(Default, Debug, PartialEq, Clone, Deserialize, Serialize)]
614#[cfg_attr(feature = "schema", derive(JsonSchema))]
615#[serde(rename_all = "camelCase", deny_unknown_fields)]
616pub struct LogicalPosition {
617  /// X coordinate.
618  pub x: f64,
619  /// Y coordinate.
620  pub y: f64,
621}
622
623/// Size of the window.
624#[derive(Default, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
625#[cfg_attr(feature = "schema", derive(JsonSchema))]
626#[serde(rename_all = "camelCase", deny_unknown_fields)]
627pub struct Size {
628  /// Width of the window.
629  pub width: u32,
630  /// Height of the window.
631  pub height: u32,
632}
633
634/// Configuration for Apple Disk Image (.dmg) bundles.
635///
636/// See more: <https://v2.tauri.app/reference/config/#dmgconfig>
637#[skip_serializing_none]
638#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
639#[cfg_attr(feature = "schema", derive(JsonSchema))]
640#[serde(rename_all = "camelCase", deny_unknown_fields)]
641pub struct DmgConfig {
642  /// Image to use as the background in dmg file. Accepted formats: `png`/`jpg`/`gif`.
643  pub background: Option<PathBuf>,
644  /// Position of volume window on screen.
645  pub window_position: Option<Position>,
646  /// Size of volume window.
647  #[serde(default = "dmg_window_size", alias = "window-size")]
648  pub window_size: Size,
649  /// Position of app file on window.
650  #[serde(default = "dmg_app_position", alias = "app-position")]
651  pub app_position: Position,
652  /// Position of application folder on window.
653  #[serde(
654    default = "dmg_application_folder_position",
655    alias = "application-folder-position"
656  )]
657  pub application_folder_position: Position,
658}
659
660impl Default for DmgConfig {
661  fn default() -> Self {
662    Self {
663      background: None,
664      window_position: None,
665      window_size: dmg_window_size(),
666      app_position: dmg_app_position(),
667      application_folder_position: dmg_application_folder_position(),
668    }
669  }
670}
671
672fn dmg_window_size() -> Size {
673  Size {
674    width: 660,
675    height: 400,
676  }
677}
678
679fn dmg_app_position() -> Position {
680  Position { x: 180, y: 170 }
681}
682
683fn dmg_application_folder_position() -> Position {
684  Position { x: 480, y: 170 }
685}
686
687fn de_macos_minimum_system_version<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
688where
689  D: Deserializer<'de>,
690{
691  let version = Option::<String>::deserialize(deserializer)?;
692  match version {
693    Some(v) if v.is_empty() => Ok(macos_minimum_system_version()),
694    e => Ok(e),
695  }
696}
697
698/// Configuration for the macOS bundles.
699///
700/// See more: <https://v2.tauri.app/reference/config/#macconfig>
701#[skip_serializing_none]
702#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
703#[cfg_attr(feature = "schema", derive(JsonSchema))]
704#[serde(rename_all = "camelCase", deny_unknown_fields)]
705pub struct MacConfig {
706  /// A list of strings indicating any macOS X frameworks that need to be bundled with the application.
707  ///
708  /// If a name is used, ".framework" must be omitted and it will look for standard install locations. You may also use a path to a specific framework.
709  pub frameworks: Option<Vec<String>>,
710  /// The files to include in the application relative to the Contents directory.
711  #[serde(default)]
712  pub files: HashMap<PathBuf, PathBuf>,
713  /// The version of the build that identifies an iteration of the bundle.
714  ///
715  /// Translates to the bundle's CFBundleVersion property.
716  #[serde(alias = "bundle-version")]
717  pub bundle_version: Option<String>,
718  /// The name of the builder that built the bundle.
719  ///
720  /// Translates to the bundle's CFBundleName property.
721  ///
722  /// If not set, defaults to the package's product name.
723  #[serde(alias = "bundle-name")]
724  pub bundle_name: Option<String>,
725  /// A version string indicating the minimum macOS X version that the bundled application supports. Defaults to `10.13`.
726  ///
727  /// Setting it to `null` completely removes the `LSMinimumSystemVersion` field on the bundle's `Info.plist`
728  /// and the `MACOSX_DEPLOYMENT_TARGET` environment variable.
729  ///
730  /// Ignored in `tauri dev`.
731  ///
732  /// An empty string is considered an invalid value so the default value is used.
733  #[serde(
734    deserialize_with = "de_macos_minimum_system_version",
735    default = "macos_minimum_system_version",
736    alias = "minimum-system-version"
737  )]
738  pub minimum_system_version: Option<String>,
739  /// Allows your application to communicate with the outside world.
740  /// It should be a lowercase, without port and protocol domain name.
741  #[serde(alias = "exception-domain")]
742  pub exception_domain: Option<String>,
743  /// Identity to use for code signing.
744  #[serde(alias = "signing-identity")]
745  pub signing_identity: Option<String>,
746  /// Whether the codesign should enable [hardened runtime](https://developer.apple.com/documentation/security/hardened_runtime) (for executables) or not.
747  #[serde(alias = "hardened-runtime", default = "default_true")]
748  pub hardened_runtime: bool,
749  /// Provider short name for notarization.
750  #[serde(alias = "provider-short-name")]
751  pub provider_short_name: Option<String>,
752  /// Path to the entitlements file.
753  pub entitlements: Option<String>,
754  /// Path to a Info.plist file to merge with the default Info.plist.
755  ///
756  /// Note that Tauri also looks for a `Info.plist` file in the same directory as the Tauri configuration file.
757  #[serde(alias = "info-plist")]
758  pub info_plist: Option<PathBuf>,
759  /// DMG-specific settings.
760  #[serde(default)]
761  pub dmg: DmgConfig,
762}
763
764impl Default for MacConfig {
765  fn default() -> Self {
766    Self {
767      frameworks: None,
768      files: HashMap::new(),
769      bundle_version: None,
770      bundle_name: None,
771      minimum_system_version: macos_minimum_system_version(),
772      exception_domain: None,
773      signing_identity: None,
774      hardened_runtime: true,
775      provider_short_name: None,
776      entitlements: None,
777      info_plist: None,
778      dmg: Default::default(),
779    }
780  }
781}
782
783fn macos_minimum_system_version() -> Option<String> {
784  Some("10.13".into())
785}
786
787fn ios_minimum_system_version() -> String {
788  "15.0".into()
789}
790
791/// Configuration for a target language for the WiX build.
792///
793/// See more: <https://v2.tauri.app/reference/config/#wixlanguageconfig>
794#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
795#[cfg_attr(feature = "schema", derive(JsonSchema))]
796#[serde(rename_all = "camelCase", deny_unknown_fields)]
797pub struct WixLanguageConfig {
798  /// The path to a locale (`.wxl`) file. See <https://wixtoolset.org/documentation/manual/v3/howtos/ui_and_localization/build_a_localized_version.html>.
799  #[serde(alias = "locale-path")]
800  pub locale_path: Option<String>,
801}
802
803/// The languages to build using WiX.
804#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
805#[cfg_attr(feature = "schema", derive(JsonSchema))]
806#[serde(untagged)]
807pub enum WixLanguage {
808  /// A single language to build, without configuration.
809  One(String),
810  /// A list of languages to build, without configuration.
811  List(Vec<String>),
812  /// A map of languages and its configuration.
813  Localized(HashMap<String, WixLanguageConfig>),
814}
815
816impl Default for WixLanguage {
817  fn default() -> Self {
818    Self::One("en-US".into())
819  }
820}
821
822/// Configuration for the MSI bundle using WiX.
823///
824/// See more: <https://v2.tauri.app/reference/config/#wixconfig>
825#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
826#[cfg_attr(feature = "schema", derive(JsonSchema))]
827#[serde(rename_all = "camelCase", deny_unknown_fields)]
828pub struct WixConfig {
829  /// MSI installer version in the format `major.minor.patch.build` (build is optional).
830  ///
831  /// Because a valid version is required for MSI installer, it will be derived from [`Config::version`] if this field is not set.
832  ///
833  /// The first field is the major version and has a maximum value of 255. The second field is the minor version and has a maximum value of 255.
834  /// The third and fourth fields have a maximum value of 65,535.
835  ///
836  /// See <https://learn.microsoft.com/en-us/windows/win32/msi/productversion> for more info.
837  pub version: Option<String>,
838  /// A GUID upgrade code for MSI installer. This code **_must stay the same across all of your updates_**,
839  /// otherwise, Windows will treat your update as a different app and your users will have duplicate versions of your app.
840  ///
841  /// By default, tauri generates this code by generating a Uuid v5 using the string `<productName>.exe.app.x64` in the DNS namespace.
842  /// You can use Tauri's CLI to generate and print this code for you, run `tauri inspect wix-upgrade-code`.
843  ///
844  /// It is recommended that you set this value in your tauri config file to avoid accidental changes in your upgrade code
845  /// whenever you want to change your product name.
846  #[serde(alias = "upgrade-code")]
847  pub upgrade_code: Option<uuid::Uuid>,
848  /// The installer languages to build. See <https://docs.microsoft.com/en-us/windows/win32/msi/localizing-the-error-and-actiontext-tables>.
849  #[serde(default)]
850  pub language: WixLanguage,
851  /// A custom .wxs template to use.
852  pub template: Option<PathBuf>,
853  /// A list of paths to .wxs files with WiX fragments to use.
854  #[serde(default, alias = "fragment-paths")]
855  pub fragment_paths: Vec<PathBuf>,
856  /// The ComponentGroup element ids you want to reference from the fragments.
857  #[serde(default, alias = "component-group-refs")]
858  pub component_group_refs: Vec<String>,
859  /// The Component element ids you want to reference from the fragments.
860  #[serde(default, alias = "component-refs")]
861  pub component_refs: Vec<String>,
862  /// The FeatureGroup element ids you want to reference from the fragments.
863  #[serde(default, alias = "feature-group-refs")]
864  pub feature_group_refs: Vec<String>,
865  /// The Feature element ids you want to reference from the fragments.
866  #[serde(default, alias = "feature-refs")]
867  pub feature_refs: Vec<String>,
868  /// The Merge element ids you want to reference from the fragments.
869  #[serde(default, alias = "merge-refs")]
870  pub merge_refs: Vec<String>,
871  /// Create an elevated update task within Windows Task Scheduler.
872  #[serde(default, alias = "enable-elevated-update-task")]
873  pub enable_elevated_update_task: bool,
874  /// Path to a bitmap file to use as the installation user interface banner.
875  /// This bitmap will appear at the top of all but the first page of the installer.
876  ///
877  /// The required dimensions are 493px × 58px.
878  #[serde(alias = "banner-path")]
879  pub banner_path: Option<PathBuf>,
880  /// Path to a bitmap file to use on the installation user interface dialogs.
881  /// It is used on the welcome and completion dialogs.
882  ///
883  /// The required dimensions are 493px × 312px.
884  #[serde(alias = "dialog-image-path")]
885  pub dialog_image_path: Option<PathBuf>,
886  /// Enables FIPS compliant algorithms.
887  /// Can also be enabled via the `TAURI_BUNDLER_WIX_FIPS_COMPLIANT` env var.
888  #[serde(default, alias = "fips-compliant")]
889  pub fips_compliant: bool,
890}
891
892/// Compression algorithms used in the NSIS installer.
893///
894/// See <https://nsis.sourceforge.io/Reference/SetCompressor>
895#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Default)]
896#[cfg_attr(feature = "schema", derive(JsonSchema))]
897#[serde(rename_all = "camelCase", deny_unknown_fields)]
898pub enum NsisCompression {
899  /// ZLIB uses the deflate algorithm, it is a quick and simple method. With the default compression level it uses about 300 KB of memory.
900  Zlib,
901  /// BZIP2 usually gives better compression ratios than ZLIB, but it is a bit slower and uses more memory. With the default compression level it uses about 4 MB of memory.
902  Bzip2,
903  /// LZMA (default) is a new compression method that gives very good compression ratios. The decompression speed is high (10-20 MB/s on a 2 GHz CPU), the compression speed is lower. The memory size that will be used for decompression is the dictionary size plus a few KBs, the default is 8 MB.
904  #[default]
905  Lzma,
906  /// Disable compression
907  None,
908}
909
910/// Install Modes for the NSIS installer.
911#[derive(Default, Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
912#[serde(rename_all = "camelCase", deny_unknown_fields)]
913#[cfg_attr(feature = "schema", derive(JsonSchema))]
914pub enum NSISInstallerMode {
915  /// Default mode for the installer.
916  ///
917  /// Install the app by default in a directory that doesn't require Administrator access.
918  ///
919  /// Installer metadata will be saved under the `HKCU` registry path.
920  #[default]
921  CurrentUser,
922  /// Install the app by default in the `Program Files` folder directory requires Administrator
923  /// access for the installation.
924  ///
925  /// Installer metadata will be saved under the `HKLM` registry path.
926  PerMachine,
927  /// Combines both modes and allows the user to choose at install time
928  /// whether to install for the current user or per machine. Note that this mode
929  /// will require Administrator access even if the user wants to install it for the current user only.
930  ///
931  /// Installer metadata will be saved under the `HKLM` or `HKCU` registry path based on the user's choice.
932  Both,
933}
934
935/// Configuration for the Installer bundle using NSIS.
936#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
937#[cfg_attr(feature = "schema", derive(JsonSchema))]
938#[serde(rename_all = "camelCase", deny_unknown_fields)]
939pub struct NsisConfig {
940  /// A custom .nsi template to use.
941  pub template: Option<PathBuf>,
942  /// The path to a bitmap file to display on the header of installers pages.
943  ///
944  /// The recommended dimensions are 150px x 57px.
945  #[serde(alias = "header-image")]
946  pub header_image: Option<PathBuf>,
947  /// The path to a bitmap file for the Welcome page and the Finish page.
948  ///
949  /// The recommended dimensions are 164px x 314px.
950  #[serde(alias = "sidebar-image")]
951  pub sidebar_image: Option<PathBuf>,
952  /// The path to an icon file used as the installer icon.
953  #[serde(alias = "installer-icon")]
954  pub installer_icon: Option<PathBuf>,
955  /// The path to an icon file used as the uninstaller icon.
956  #[serde(alias = "uninstaller-icon")]
957  pub uninstaller_icon: Option<PathBuf>,
958  /// The path to a bitmap file to display on the header of uninstallers pages.
959  /// Defaults to [`Self::header_image`]. If this is set but [`Self::header_image`] is not, a default image from NSIS will be applied to `header_image`
960  ///
961  /// The recommended dimensions are 150px x 57px.
962  #[serde(alias = "uninstaller-header-image")]
963  pub uninstaller_header_image: Option<PathBuf>,
964  /// Whether the installation will be for all users or just the current user.
965  #[serde(default, alias = "install-mode")]
966  pub install_mode: NSISInstallerMode,
967  /// A list of installer languages. Default to `["English"]` if not set.
968  ///
969  /// By default the OS language is used. If the OS language is not in the list of languages, the first language will be used.
970  /// To allow the user to select the language, set `display_language_selector` to `true`.
971  ///
972  /// See <https://github.com/kichik/nsis/tree/9465c08046f00ccb6eda985abbdbf52c275c6c4d/Contrib/Language%20files> for the complete list of languages.
973  pub languages: Option<Vec<String>>,
974  /// A key-value pair where the key is the language and the
975  /// value is the path to a custom `.nsh` file that holds the translated text for tauri's custom messages.
976  ///
977  /// See <https://github.com/tauri-apps/tauri/blob/dev/crates/tauri-bundler/src/bundle/windows/nsis/languages/English.nsh> for an example `.nsh` file.
978  ///
979  /// **Note**: the key must be a valid NSIS language and it must be added to the [`Self::languages`] array,
980  pub custom_language_files: Option<HashMap<String, PathBuf>>,
981  /// Whether to display a language selector dialog before the installer and uninstaller windows are rendered or not.
982  /// By default the OS language is selected, with a fallback to the first language in the `languages` array.
983  #[serde(default, alias = "display-language-selector")]
984  pub display_language_selector: bool,
985  /// Set the compression algorithm used to compress files in the installer.
986  ///
987  /// See <https://nsis.sourceforge.io/Reference/SetCompressor>
988  #[serde(default)]
989  pub compression: NsisCompression,
990  /// Set the folder name for the start menu shortcut.
991  ///
992  /// Use this option if you have multiple apps and wish to group their shortcuts under one folder
993  /// or if you generally prefer to set your shortcut inside a folder.
994  ///
995  /// Examples:
996  /// - `AwesomePublisher`, shortcut will be placed in `%AppData%\Microsoft\Windows\Start Menu\Programs\AwesomePublisher\<your-app>.lnk`
997  /// - If unset, shortcut will be placed in `%AppData%\Microsoft\Windows\Start Menu\Programs\<your-app>.lnk`
998  #[serde(alias = "start-menu-folder")]
999  pub start_menu_folder: Option<String>,
1000  /// A path to a `.nsh` file that contains special NSIS macros to be hooked into the
1001  /// main installer.nsi script.
1002  ///
1003  /// Supported hooks are:
1004  ///
1005  /// - `NSIS_HOOK_PREINSTALL`: This hook runs before copying files, setting registry key values and creating shortcuts.
1006  /// - `NSIS_HOOK_POSTINSTALL`: This hook runs after the installer has finished copying all files, setting the registry keys and created shortcuts.
1007  /// - `NSIS_HOOK_PREUNINSTALL`: This hook runs before removing any files, registry keys and shortcuts.
1008  /// - `NSIS_HOOK_POSTUNINSTALL`: This hook runs after files, registry keys and shortcuts have been removed.
1009  ///
1010  /// ### Example
1011  ///
1012  /// ```nsh
1013  /// !macro NSIS_HOOK_PREINSTALL
1014  ///   MessageBox MB_OK "PreInstall"
1015  /// !macroend
1016  ///
1017  /// !macro NSIS_HOOK_POSTINSTALL
1018  ///   MessageBox MB_OK "PostInstall"
1019  /// !macroend
1020  ///
1021  /// !macro NSIS_HOOK_PREUNINSTALL
1022  ///   MessageBox MB_OK "PreUnInstall"
1023  /// !macroend
1024  ///
1025  /// !macro NSIS_HOOK_POSTUNINSTALL
1026  ///   MessageBox MB_OK "PostUninstall"
1027  /// !macroend
1028  /// ```
1029  #[serde(alias = "installer-hooks")]
1030  pub installer_hooks: Option<PathBuf>,
1031  /// Deprecated: use [`WindowsConfig::minimum_webview2_version`] (`bundle >  windows > minimumWebview2Version`) instead.
1032  ///
1033  /// Try to ensure that the WebView2 version is equal to or newer than this version,
1034  /// if the user's WebView2 is older than this version,
1035  /// the installer will try to trigger a WebView2 update.
1036  #[deprecated(
1037    since = "2.10.0",
1038    note = "Use `WindowsConfig::minimum_webview2_version` instead."
1039  )]
1040  #[serde(alias = "minimum-webview2-version")]
1041  pub minimum_webview2_version: Option<String>,
1042}
1043
1044/// Install modes for the Webview2 runtime.
1045/// Note that for the updater bundle [`Self::DownloadBootstrapper`] is used.
1046///
1047/// For more information see <https://v2.tauri.app/distribute/windows-installer/#webview2-installation-options>.
1048#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
1049#[serde(tag = "type", rename_all = "camelCase", deny_unknown_fields)]
1050#[cfg_attr(feature = "schema", derive(JsonSchema))]
1051pub enum WebviewInstallMode {
1052  /// Do not install the Webview2 as part of the Windows Installer.
1053  Skip,
1054  /// Download the bootstrapper and run it.
1055  /// Requires an internet connection.
1056  /// Results in a smaller installer size, but is not recommended on Windows 7.
1057  DownloadBootstrapper {
1058    /// Instructs the installer to run the bootstrapper in silent mode. Defaults to `true`.
1059    #[serde(default = "default_true")]
1060    silent: bool,
1061  },
1062  /// Embed the bootstrapper and run it.
1063  /// Requires an internet connection.
1064  /// Increases the installer size by around 1.8MB, but offers better support on Windows 7.
1065  EmbedBootstrapper {
1066    /// Instructs the installer to run the bootstrapper in silent mode. Defaults to `true`.
1067    #[serde(default = "default_true")]
1068    silent: bool,
1069  },
1070  /// Embed the offline installer and run it.
1071  /// Does not require an internet connection.
1072  /// Increases the installer size by around 127MB.
1073  OfflineInstaller {
1074    /// Instructs the installer to run the installer in silent mode. Defaults to `true`.
1075    #[serde(default = "default_true")]
1076    silent: bool,
1077  },
1078  /// Embed a fixed webview2 version and use it at runtime.
1079  /// Increases the installer size by around 180MB.
1080  FixedRuntime {
1081    /// The path to the fixed runtime to use.
1082    ///
1083    /// The fixed version can be downloaded [on the official website](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section).
1084    /// The `.cab` file must be extracted to a folder and this folder path must be defined on this field.
1085    path: PathBuf,
1086  },
1087}
1088
1089impl Default for WebviewInstallMode {
1090  fn default() -> Self {
1091    Self::DownloadBootstrapper { silent: true }
1092  }
1093}
1094
1095/// Custom Signing Command configuration.
1096#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1097#[cfg_attr(feature = "schema", derive(JsonSchema))]
1098#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
1099pub enum CustomSignCommandConfig {
1100  /// A string notation of the script to execute.
1101  ///
1102  /// "%1" will be replaced with the path to the binary to be signed.
1103  ///
1104  /// This is a simpler notation for the command.
1105  /// Tauri will split the string with `' '` and use the first element as the command name and the rest as arguments.
1106  ///
1107  /// If you need to use whitespace in the command or arguments, use the object notation [`Self::CommandWithOptions`].
1108  Command(String),
1109  /// An object notation of the command.
1110  ///
1111  /// This is more complex notation for the command but
1112  /// this allows you to use whitespace in the command and arguments.
1113  CommandWithOptions {
1114    /// The command to run to sign the binary.
1115    cmd: String,
1116    /// The arguments to pass to the command.
1117    ///
1118    /// "%1" will be replaced with the path to the binary to be signed.
1119    args: Vec<String>,
1120  },
1121}
1122
1123/// Windows bundler configuration.
1124///
1125/// See more: <https://v2.tauri.app/reference/config/#windowsconfig>
1126#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1127#[cfg_attr(feature = "schema", derive(JsonSchema))]
1128#[serde(rename_all = "camelCase", deny_unknown_fields)]
1129pub struct WindowsConfig {
1130  /// Specifies the file digest algorithm to use for creating file signatures.
1131  /// Required for code signing. SHA-256 is recommended.
1132  #[serde(alias = "digest-algorithm")]
1133  pub digest_algorithm: Option<String>,
1134  /// Specifies the SHA1 hash of the signing certificate.
1135  #[serde(alias = "certificate-thumbprint")]
1136  pub certificate_thumbprint: Option<String>,
1137  /// Server to use during timestamping.
1138  #[serde(alias = "timestamp-url")]
1139  pub timestamp_url: Option<String>,
1140  /// Whether to use Time-Stamp Protocol (TSP, a.k.a. RFC 3161) for the timestamp server. Your code signing provider may
1141  /// use a TSP timestamp server, like e.g. SSL.com does. If so, enable TSP by setting to true.
1142  #[serde(default)]
1143  pub tsp: bool,
1144  /// The installation mode for the Webview2 runtime.
1145  #[serde(default, alias = "webview-install-mode")]
1146  pub webview_install_mode: WebviewInstallMode,
1147  /// Whether an older version can be installed when a newer one is already installed.
1148  ///
1149  /// For instance, if `1.2.1` is installed and this is set to `false`:
1150  ///
1151  /// - The MSI installer won't install version `1.2.0` or `1.1.5`.
1152  /// - The NSIS installer won't install them over `1.2.1` in silent, passive or update mode.
1153  ///   The interactive installer only lets the user install them after uninstalling `1.2.1` first.
1154  ///
1155  /// The default value of this flag is `true`.
1156  #[serde(default = "default_true", alias = "allow-downgrades")]
1157  pub allow_downgrades: bool,
1158  /// Try to ensure that the WebView2 version is equal to or newer than this version,
1159  /// if the user's WebView2 is older than this version,
1160  /// the installer will try to trigger a WebView2 update.
1161  #[serde(alias = "minimum-webview2-version")]
1162  pub minimum_webview2_version: Option<String>,
1163  /// Configuration for the MSI generated with WiX.
1164  pub wix: Option<WixConfig>,
1165  /// Configuration for the installer generated with NSIS.
1166  pub nsis: Option<NsisConfig>,
1167  /// Specify a custom command to sign the binaries.
1168  /// This command needs to have a `%1` in args which is just a placeholder for the binary path,
1169  /// which we will detect and replace before calling the command.
1170  ///
1171  /// By Default we use `signtool.exe` which can be found only on Windows so
1172  /// if you are on another platform and want to cross-compile and sign you will
1173  /// need to use another tool like `osslsigncode`.
1174  #[serde(alias = "sign-command")]
1175  pub sign_command: Option<CustomSignCommandConfig>,
1176  /// Whether to bundle the Visual C++ runtime DLLs alongside the application.
1177  ///
1178  /// This can be particularly useful when your application includes sidecars or DLLs that do
1179  /// not statically link the Visual C++ runtime and require the runtime DLLs at runtime, and
1180  /// you do not want to require users to install the Visual C++ Redistributable. This can also
1181  /// be useful when `build > windows > staticVCRuntime` is set to `false`.
1182  #[serde(
1183    default,
1184    rename = "bundleVCRuntime",
1185    alias = "bundle-vc-runtime",
1186    alias = "bundleVcRuntime"
1187  )]
1188  pub bundle_vc_runtime: bool,
1189}
1190
1191impl Default for WindowsConfig {
1192  fn default() -> Self {
1193    Self {
1194      digest_algorithm: None,
1195      certificate_thumbprint: None,
1196      timestamp_url: None,
1197      tsp: false,
1198      webview_install_mode: Default::default(),
1199      allow_downgrades: true,
1200      minimum_webview2_version: None,
1201      wix: None,
1202      nsis: None,
1203      sign_command: None,
1204      bundle_vc_runtime: false,
1205    }
1206  }
1207}
1208
1209/// macOS-only. Corresponds to CFBundleTypeRole
1210#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1211#[cfg_attr(feature = "schema", derive(JsonSchema))]
1212pub enum BundleTypeRole {
1213  /// CFBundleTypeRole.Editor. Files can be read and edited.
1214  #[default]
1215  Editor,
1216  /// CFBundleTypeRole.Viewer. Files can be read.
1217  Viewer,
1218  /// CFBundleTypeRole.Shell
1219  Shell,
1220  /// CFBundleTypeRole.QLGenerator
1221  QLGenerator,
1222  /// CFBundleTypeRole.None
1223  None,
1224}
1225
1226impl Display for BundleTypeRole {
1227  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1228    match self {
1229      Self::Editor => write!(f, "Editor"),
1230      Self::Viewer => write!(f, "Viewer"),
1231      Self::Shell => write!(f, "Shell"),
1232      Self::QLGenerator => write!(f, "QLGenerator"),
1233      Self::None => write!(f, "None"),
1234    }
1235  }
1236}
1237
1238// Issue #13159 - Missing the LSHandlerRank and Apple warns after uploading to App Store Connect.
1239// https://github.com/tauri-apps/tauri/issues/13159
1240/// Corresponds to LSHandlerRank
1241#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1242#[cfg_attr(feature = "schema", derive(JsonSchema))]
1243pub enum HandlerRank {
1244  /// LSHandlerRank.Default. This app is an opener of files of this type; this value is also used if no rank is specified.
1245  #[default]
1246  Default,
1247  /// LSHandlerRank.Owner. This app is the primary creator of files of this type.
1248  Owner,
1249  /// LSHandlerRank.Alternate. This app is a secondary viewer of files of this type.
1250  Alternate,
1251  /// LSHandlerRank.None. This app is never selected to open files of this type, but it accepts drops of files of this type.
1252  None,
1253}
1254
1255impl Display for HandlerRank {
1256  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1257    match self {
1258      Self::Default => write!(f, "Default"),
1259      Self::Owner => write!(f, "Owner"),
1260      Self::Alternate => write!(f, "Alternate"),
1261      Self::None => write!(f, "None"),
1262    }
1263  }
1264}
1265
1266/// An extension for a [`FileAssociation`].
1267///
1268/// A leading `.` is automatically stripped.
1269#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
1270#[cfg_attr(feature = "schema", derive(JsonSchema))]
1271pub struct AssociationExt(pub String);
1272
1273impl fmt::Display for AssociationExt {
1274  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1275    write!(f, "{}", self.0)
1276  }
1277}
1278
1279impl<'d> serde::Deserialize<'d> for AssociationExt {
1280  fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {
1281    let ext = String::deserialize(deserializer)?;
1282    if let Some(ext) = ext.strip_prefix('.') {
1283      Ok(AssociationExt(ext.into()))
1284    } else {
1285      Ok(AssociationExt(ext))
1286    }
1287  }
1288}
1289
1290/// File association
1291#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1292#[cfg_attr(feature = "schema", derive(JsonSchema))]
1293#[serde(rename_all = "camelCase", deny_unknown_fields)]
1294pub struct FileAssociation {
1295  /// File extensions to associate with this app. e.g. 'png'
1296  pub ext: Vec<AssociationExt>,
1297  /// Declare support to a file with the given content type. Maps to `LSItemContentTypes` on macOS.
1298  ///
1299  /// This allows supporting any file format declared by another application that conforms to this type.
1300  /// Declaration of new types can be done with [`Self::exported_type`] and linking to certain content types are done via [`ExportedFileAssociation::conforms_to`].
1301  #[serde(alias = "content-types")]
1302  pub content_types: Option<Vec<String>>,
1303  /// The name. Maps to `CFBundleTypeName` on macOS. Default to `ext[0]`
1304  pub name: Option<String>,
1305  /// The association description. Windows-only. It is displayed on the `Type` column on Windows Explorer.
1306  pub description: Option<String>,
1307  /// The app's role with respect to the type. Maps to `CFBundleTypeRole` on macOS.
1308  #[serde(default)]
1309  pub role: BundleTypeRole,
1310  /// The mime-type of the association, e.g. `'image/png'` or `'text/plain'`.
1311  ///
1312  /// - **Linux**: written as `MimeType=` in the `.desktop` file.
1313  /// - **macOS / iOS**: added as `public.mime-type` in the `UTTypeTagSpecification` dictionary of
1314  ///   the `UTExportedTypeDeclarations` entry in `Info.plist`.
1315  /// - **Android**: used as `android:mimeType` in the `<data>` element of an `<intent-filter>`
1316  ///   in `AndroidManifest.xml`.
1317  #[serde(alias = "mime-type")]
1318  pub mime_type: Option<String>,
1319  /// The ranking of this app among apps that declare themselves as editors or viewers of the given file type.  Maps to `LSHandlerRank` on macOS.
1320  #[serde(default)]
1321  pub rank: HandlerRank,
1322  /// The exported type definition. Maps to a `UTExportedTypeDeclarations` entry on macOS.
1323  ///
1324  /// You should define this if the associated file is a custom file type defined by your application.
1325  pub exported_type: Option<ExportedFileAssociation>,
1326  /// Intent action filters for this file association.
1327  ///
1328  /// By default all filters are used.
1329  #[serde(alias = "android-intent-action-filters")]
1330  pub android_intent_action_filters: Option<Vec<AndroidIntentAction>>,
1331}
1332
1333/// Android intent action.
1334#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Hash)]
1335#[cfg_attr(feature = "schema", derive(JsonSchema))]
1336#[serde(rename_all = "camelCase")]
1337#[non_exhaustive]
1338pub enum AndroidIntentAction {
1339  /// ACTION_SEND.
1340  ///
1341  /// <https://developer.android.com/reference/android/content/Intent#ACTION_SEND>
1342  Send,
1343  /// ACTION_SEND_MULTIPLE.
1344  ///
1345  /// <https://developer.android.com/reference/android/content/Intent#ACTION_SEND_MULTIPLE>
1346  SendMultiple,
1347  /// ACTION_VIEW.
1348  ///
1349  /// <https://developer.android.com/reference/android/content/Intent#ACTION_SEND>
1350  View,
1351}
1352
1353/// The exported type definition. Maps to a `UTExportedTypeDeclarations` entry on macOS.
1354#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1355#[cfg_attr(feature = "schema", derive(JsonSchema))]
1356#[serde(rename_all = "camelCase", deny_unknown_fields)]
1357pub struct ExportedFileAssociation {
1358  /// The unique identifier for the exported type. Maps to `UTTypeIdentifier`.
1359  pub identifier: String,
1360  /// The types that this type conforms to. Maps to `UTTypeConformsTo`.
1361  ///
1362  /// Examples are `public.data`, `public.image`, `public.json` and `public.database`.
1363  #[serde(alias = "conforms-to")]
1364  pub conforms_to: Option<Vec<String>>,
1365}
1366
1367impl FileAssociation {
1368  /// Infers UTIs (Uniform Type Identifiers) from file extensions and mime types.
1369  /// This is useful for macOS and iOS to automatically populate `LSItemContentTypes`
1370  /// in the Info.plist for share sheet and file association support.
1371  ///
1372  /// Returns a vector of UTIs that should be included in `LSItemContentTypes`.
1373  /// Explicitly provided content types are included first, followed by inferred types.
1374  pub fn infer_content_types(&self) -> HashSet<String> {
1375    let mut content_types = HashSet::new();
1376
1377    // when we have an exported type, we only reference it
1378    if let Some(exported_type) = &self.exported_type {
1379      content_types.insert(exported_type.identifier.clone());
1380      return content_types;
1381    }
1382
1383    // Start with explicitly provided content types
1384    if let Some(explicit_types) = &self.content_types {
1385      content_types.extend(explicit_types.iter().cloned());
1386    }
1387
1388    // Infer from extensions and add to content_types (avoiding duplicates)
1389    for ext in &self.ext {
1390      if let Some(uti) = extension_to_uti(&ext.0) {
1391        content_types.insert(uti.to_string());
1392      }
1393    }
1394
1395    // Also infer from mime type if available (avoiding duplicates)
1396    if let Some(mime_type) = &self.mime_type
1397      && let Some(uti) = mime_type_to_uti(mime_type)
1398    {
1399      content_types.insert(uti.to_string());
1400    }
1401
1402    content_types
1403  }
1404}
1405
1406/// Generates plist dictionary entries for file associations.
1407/// This is used by both macOS and iOS bundlers to populate Info.plist.
1408///
1409/// Returns a plist dictionary containing `UTExportedTypeDeclarations` and `CFBundleDocumentTypes`
1410/// if there are any file associations configured.
1411pub fn file_associations_plist(associations: &[FileAssociation]) -> Option<plist::Value> {
1412  use plist::{Dictionary, Value};
1413
1414  if associations.is_empty() {
1415    return None;
1416  }
1417
1418  let exported_associations = associations
1419    .iter()
1420    .filter_map(|association| {
1421      association.exported_type.as_ref().map(|exported_type| {
1422        let mut dict = Dictionary::new();
1423
1424        dict.insert(
1425          "UTTypeIdentifier".into(),
1426          exported_type.identifier.clone().into(),
1427        );
1428        if let Some(description) = &association.description {
1429          dict.insert("UTTypeDescription".into(), description.clone().into());
1430        }
1431        if let Some(conforms_to) = &exported_type.conforms_to {
1432          dict.insert(
1433            "UTTypeConformsTo".into(),
1434            Value::Array(conforms_to.iter().map(|s| s.clone().into()).collect()),
1435          );
1436        }
1437
1438        let mut specification = Dictionary::new();
1439        specification.insert(
1440          "public.filename-extension".into(),
1441          Value::Array(
1442            association
1443              .ext
1444              .iter()
1445              .map(|s| s.to_string().into())
1446              .collect(),
1447          ),
1448        );
1449        if let Some(mime_type) = &association.mime_type {
1450          specification.insert("public.mime-type".into(), mime_type.clone().into());
1451        }
1452
1453        dict.insert("UTTypeTagSpecification".into(), specification.into());
1454
1455        Value::Dictionary(dict)
1456      })
1457    })
1458    .collect::<Vec<_>>();
1459
1460  let document_types = associations
1461    .iter()
1462    .map(|association| {
1463      let mut dict = Dictionary::new();
1464
1465      if !association.ext.is_empty() {
1466        dict.insert(
1467          "CFBundleTypeExtensions".into(),
1468          Value::Array(
1469            association
1470              .ext
1471              .iter()
1472              .map(|ext| ext.to_string().into())
1473              .collect(),
1474          ),
1475        );
1476      }
1477
1478      // For macOS/iOS share sheet, we need LSItemContentTypes with standard UTIs
1479      let content_types = association.infer_content_types();
1480
1481      // Add LSItemContentTypes if we have any content types
1482      if !content_types.is_empty() {
1483        dict.insert(
1484          "LSItemContentTypes".into(),
1485          Value::Array(content_types.iter().map(|s| s.clone().into()).collect()),
1486        );
1487      }
1488
1489      let type_name = association
1490        .name
1491        .clone()
1492        .or_else(|| association.ext.first().map(|ext| ext.0.clone()))
1493        .unwrap_or_default();
1494      dict.insert("CFBundleTypeName".into(), type_name.into());
1495      dict.insert(
1496        "CFBundleTypeRole".into(),
1497        association.role.to_string().into(),
1498      );
1499      dict.insert("LSHandlerRank".into(), association.rank.to_string().into());
1500
1501      Value::Dictionary(dict)
1502    })
1503    .collect::<Vec<_>>();
1504
1505  if exported_associations.is_empty() && document_types.is_empty() {
1506    return None;
1507  }
1508
1509  let mut plist = Dictionary::new();
1510  if !exported_associations.is_empty() {
1511    plist.insert(
1512      "UTExportedTypeDeclarations".into(),
1513      Value::Array(exported_associations),
1514    );
1515  }
1516  if !document_types.is_empty() {
1517    plist.insert("CFBundleDocumentTypes".into(), Value::Array(document_types));
1518  }
1519
1520  Some(Value::Dictionary(plist))
1521}
1522
1523/// Maps file extensions to their standard UTIs for macOS/iOS share sheet support
1524fn extension_to_uti(ext: &str) -> Option<&'static str> {
1525  match ext.to_lowercase().as_str() {
1526    // Images
1527    "png" => Some("public.png"),
1528    "jpg" | "jpeg" => Some("public.jpeg"),
1529    "gif" => Some("com.compuserve.gif"),
1530    "bmp" => Some("com.microsoft.bmp"),
1531    "tiff" | "tif" => Some("public.tiff"),
1532    "ico" => Some("com.microsoft.ico"),
1533    "heic" | "heif" => Some("public.heif-standard-image"),
1534    "webp" => Some("org.webmproject.webp"),
1535    "svg" => Some("public.svg-image"),
1536    // Videos
1537    "mp4" => Some("public.mpeg-4"),
1538    "mov" => Some("com.apple.quicktime-movie"),
1539    "avi" => Some("public.avi"),
1540    "mkv" => Some("public.mpeg-4"),
1541    // Audio
1542    "mp3" => Some("public.mp3"),
1543    "wav" => Some("com.microsoft.waveform-audio"),
1544    "aac" => Some("public.aac-audio"),
1545    "m4a" => Some("public.mpeg-4-audio"),
1546    // Documents
1547    "pdf" => Some("com.adobe.pdf"),
1548    "txt" => Some("public.plain-text"),
1549    "rtf" => Some("public.rtf"),
1550    "html" | "htm" => Some("public.html"),
1551    "json" => Some("public.json"),
1552    "xml" => Some("public.xml"),
1553    _ => None,
1554  }
1555}
1556
1557/// Infers UTIs from mime type
1558fn mime_type_to_uti(mime_type: &str) -> Option<&'static str> {
1559  match mime_type {
1560    "image/png" => Some("public.png"),
1561    "image/jpeg" | "image/jpg" => Some("public.jpeg"),
1562    "image/gif" => Some("com.compuserve.gif"),
1563    "image/bmp" => Some("com.microsoft.bmp"),
1564    "image/tiff" => Some("public.tiff"),
1565    "image/heic" | "image/heif" => Some("public.heif-standard-image"),
1566    "image/webp" => Some("org.webmproject.webp"),
1567    "image/svg+xml" => Some("public.svg-image"),
1568    mime if mime.starts_with("image/") => Some("public.image"),
1569    "video/mp4" => Some("public.mpeg-4"),
1570    "video/quicktime" => Some("com.apple.quicktime-movie"),
1571    "video/x-msvideo" => Some("public.avi"),
1572    mime if mime.starts_with("video/") => Some("public.movie"),
1573    "audio/mpeg" | "audio/mp3" => Some("public.mp3"),
1574    "audio/wav" | "audio/wave" => Some("com.microsoft.waveform-audio"),
1575    "audio/aac" => Some("public.aac-audio"),
1576    "audio/mp4" => Some("public.mpeg-4-audio"),
1577    mime if mime.starts_with("audio/") => Some("public.audio"),
1578    "application/pdf" => Some("com.adobe.pdf"),
1579    "text/plain" => Some("public.plain-text"),
1580    "text/rtf" => Some("public.rtf"),
1581    "text/html" => Some("public.html"),
1582    "application/json" => Some("public.json"),
1583    "application/xml" | "text/xml" => Some("public.xml"),
1584    _ => None,
1585  }
1586}
1587
1588/// Deep link protocol configuration.
1589#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1590#[cfg_attr(feature = "schema", derive(JsonSchema))]
1591#[serde(rename_all = "camelCase", deny_unknown_fields)]
1592pub struct DeepLinkProtocol {
1593  /// URL schemes to associate with this app without `://`. For example `my-app`
1594  #[serde(default)]
1595  pub schemes: Vec<String>,
1596  /// Domains to associate with this app. For example `example.com`.
1597  /// Currently only supported on macOS, translating to an [universal app link].
1598  ///
1599  /// Note that universal app links require signed apps with a provisioning profile to work.
1600  /// You can accomplish that by including the `embedded.provisionprofile` file in the `macOS > files` option.
1601  ///
1602  /// [universal app link]: https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app
1603  #[serde(default)]
1604  pub domains: Vec<String>,
1605  /// The protocol name. **macOS-only** and maps to `CFBundleTypeName`. Defaults to `<bundle-id>.<schemes[0]>`
1606  pub name: Option<String>,
1607  /// The app's role for these schemes. **macOS-only** and maps to `CFBundleTypeRole`.
1608  #[serde(default)]
1609  pub role: BundleTypeRole,
1610}
1611
1612/// Definition for bundle resources.
1613/// Can be either a list of paths to include or a map of source to target paths.
1614#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1615#[cfg_attr(feature = "schema", derive(JsonSchema))]
1616#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
1617pub enum BundleResources {
1618  /// A list of paths to include.
1619  List(Vec<String>),
1620  /// A map of source to target paths.
1621  Map(HashMap<String, String>),
1622}
1623
1624impl BundleResources {
1625  /// Adds a path to the resource collection.
1626  pub fn push(&mut self, path: impl Into<String>) {
1627    match self {
1628      Self::List(l) => l.push(path.into()),
1629      Self::Map(l) => {
1630        let path = path.into();
1631        l.insert(path.clone(), path);
1632      }
1633    }
1634  }
1635}
1636
1637/// Updater type
1638#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1639#[cfg_attr(feature = "schema", derive(JsonSchema))]
1640#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
1641pub enum Updater {
1642  /// Generates legacy zipped v1 compatible updaters
1643  String(V1Compatible),
1644  /// Produce updaters and their signatures or not
1645  // Can't use untagged on enum field here: https://github.com/GREsau/schemars/issues/222
1646  Bool(bool),
1647}
1648
1649impl Default for Updater {
1650  fn default() -> Self {
1651    Self::Bool(false)
1652  }
1653}
1654
1655/// Generates legacy zipped v1 compatible updaters
1656#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1657#[cfg_attr(feature = "schema", derive(JsonSchema))]
1658#[serde(rename_all = "camelCase", deny_unknown_fields)]
1659pub enum V1Compatible {
1660  /// Generates legacy zipped v1 compatible updaters
1661  V1Compatible,
1662}
1663
1664/// Configuration for tauri-bundler.
1665///
1666/// See more: <https://v2.tauri.app/reference/config/#bundleconfig>
1667#[skip_serializing_none]
1668#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1669#[cfg_attr(feature = "schema", derive(JsonSchema))]
1670#[serde(rename_all = "camelCase", deny_unknown_fields)]
1671pub struct BundleConfig {
1672  /// Whether Tauri should bundle your application or just output the executable.
1673  #[serde(default)]
1674  pub active: bool,
1675  /// The bundle targets, currently supports ["deb", "rpm", "appimage", "nsis", "msi", "app", "dmg"] or "all".
1676  #[serde(default)]
1677  pub targets: BundleTarget,
1678  #[serde(default)]
1679  /// Produce updaters and their signatures or not
1680  pub create_updater_artifacts: Updater,
1681  /// The application's publisher. Defaults to the second element in the identifier string.
1682  ///
1683  /// Currently maps to the Manufacturer property of the Windows Installer
1684  /// and the Maintainer field of debian packages if the Cargo.toml does not have the authors field.
1685  pub publisher: Option<String>,
1686  /// A url to the home page of your application. If unset, will
1687  /// fallback to `homepage` defined in `Cargo.toml`.
1688  ///
1689  /// Supported bundle targets: `deb`, `rpm`, `nsis` and `msi`.
1690  pub homepage: Option<String>,
1691  /// The app's icons
1692  #[serde(default)]
1693  pub icon: Vec<String>,
1694  /// App resources to bundle.
1695  /// Each resource is a path to a file or directory.
1696  /// Glob patterns are supported.
1697  ///
1698  /// ## Examples
1699  ///
1700  /// To include a list of files:
1701  ///
1702  /// ```json
1703  /// {
1704  ///   "bundle": {
1705  ///     "resources": [
1706  ///       "./path/to/some-file.txt",
1707  ///       "/absolute/path/to/textfile.txt",
1708  ///       "../relative/path/to/jsonfile.json",
1709  ///       "some-folder/",
1710  ///       "resources/**/*.md"
1711  ///     ]
1712  ///   }
1713  /// }
1714  /// ```
1715  ///
1716  /// The bundled files will be in `$RESOURCES/` with the original directory structure preserved,
1717  /// for example: `./path/to/some-file.txt` -> `$RESOURCE/path/to/some-file.txt`
1718  ///
1719  /// To fine control where the files will get copied to, use a map instead
1720  ///
1721  /// ```json
1722  /// {
1723  ///   "bundle": {
1724  ///     "resources": {
1725  ///       "/absolute/path/to/textfile.txt": "resources/textfile.txt",
1726  ///       "relative/path/to/jsonfile.json": "resources/jsonfile.json",
1727  ///       "resources/": "",
1728  ///       "docs/**/*md": "website-docs/"
1729  ///     }
1730  ///   }
1731  /// }
1732  /// ```
1733  ///
1734  /// Note that when using glob pattern in this case, the original directory structure is not preserved,
1735  /// everything gets copied to the target directory directly
1736  ///
1737  /// See more: <https://v2.tauri.app/develop/resources/>
1738  pub resources: Option<BundleResources>,
1739  /// A copyright string associated with your application.
1740  pub copyright: Option<String>,
1741  /// The package's license identifier to be included in the appropriate bundles.
1742  /// If not set, defaults to the license from the Cargo.toml file.
1743  pub license: Option<String>,
1744  /// The path to the license file to be included in the appropriate bundles.
1745  #[serde(alias = "license-file")]
1746  pub license_file: Option<PathBuf>,
1747  /// The application kind.
1748  ///
1749  /// Should be one of the following:
1750  /// Business, DeveloperTool, Education, Entertainment, Finance, Game, ActionGame, AdventureGame, ArcadeGame, BoardGame, CardGame, CasinoGame, DiceGame, EducationalGame, FamilyGame, KidsGame, MusicGame, PuzzleGame, RacingGame, RolePlayingGame, SimulationGame, SportsGame, StrategyGame, TriviaGame, WordGame, GraphicsAndDesign, HealthcareAndFitness, Lifestyle, Medical, Music, News, Photography, Productivity, Reference, SocialNetworking, Sports, Travel, Utility, Video, Weather.
1751  pub category: Option<String>,
1752  /// File types to associate with the application.
1753  pub file_associations: Option<Vec<FileAssociation>>,
1754  /// A short description of your application.
1755  #[serde(alias = "short-description")]
1756  pub short_description: Option<String>,
1757  /// A longer, multi-line description of the application.
1758  #[serde(alias = "long-description")]
1759  pub long_description: Option<String>,
1760  /// Whether to use the project's `target` directory, for caching build tools (e.g., Wix and NSIS) when building this application. Defaults to `false`.
1761  ///
1762  /// If true, tools will be cached in `target/.tauri/`.
1763  /// If false, tools will be cached in the current user's platform-specific cache directory.
1764  ///
1765  /// An example where it can be appropriate to set this to `true` is when building this application as a Windows System user (e.g., AWS EC2 workloads),
1766  /// because the Window system's app data directory is restricted.
1767  #[serde(default, alias = "use-local-tools-dir")]
1768  pub use_local_tools_dir: bool,
1769  /// A list of—either absolute or relative—paths to binaries to embed with your application.
1770  ///
1771  /// Note that Tauri will look for system-specific binaries following the pattern "binary-name{-target-triple}{.system-extension}".
1772  ///
1773  /// E.g. for the external binary "my-binary", Tauri looks for:
1774  ///
1775  /// - "my-binary-x86_64-pc-windows-msvc.exe" for Windows
1776  /// - "my-binary-x86_64-apple-darwin" for macOS
1777  /// - "my-binary-x86_64-unknown-linux-gnu" for Linux
1778  ///
1779  /// so don't forget to provide binaries for all targeted platforms.
1780  #[serde(alias = "external-bin")]
1781  pub external_bin: Option<Vec<String>>,
1782  /// Configuration for the Windows bundles.
1783  #[serde(default)]
1784  pub windows: WindowsConfig,
1785  /// Configuration for the Linux bundles.
1786  #[serde(default)]
1787  pub linux: LinuxConfig,
1788  /// Configuration for the macOS bundles.
1789  #[serde(rename = "macOS", alias = "macos", default)]
1790  pub macos: MacConfig,
1791  /// iOS configuration.
1792  #[serde(rename = "iOS", alias = "ios", default)]
1793  pub ios: IosConfig,
1794  /// Android configuration.
1795  #[serde(default)]
1796  pub android: AndroidConfig,
1797  /// Configuration for apps using the Chromium Embedded Framework.
1798  #[serde(default)]
1799  pub cef: CefConfig,
1800}
1801
1802/// Configuration for apps using the Chromium Embedded Framework (the `cef`
1803/// feature of the `tauri` crate).
1804///
1805/// See more: <https://v2.tauri.app/reference/config/#cefconfig>
1806#[skip_serializing_none]
1807#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1808#[cfg_attr(feature = "schema", derive(JsonSchema))]
1809#[serde(rename_all = "camelCase", deny_unknown_fields)]
1810pub struct CefConfig {
1811  /// Whether the CEF binary distribution is embedded in the bundle.
1812  /// Defaults to `true`.
1813  ///
1814  /// Set it to `false` for an app that loads CEF at run time from outside
1815  /// its own bundle — a shared, machine-wide runtime, through the
1816  /// `TAURI_CEF_LIBRARY_PATH` environment variable. The framework,
1817  /// `libcef` and their resources are then left out of every bundle, and
1818  /// the app must find a runtime at launch or it will not start. On macOS
1819  /// the helper apps are still produced: they belong to the app, not to
1820  /// the distribution.
1821  #[serde(default = "default_true")]
1822  pub embed: bool,
1823}
1824
1825impl Default for CefConfig {
1826  fn default() -> Self {
1827    Self { embed: true }
1828  }
1829}
1830
1831/// A tuple struct of RGBA colors. Each value has minimum of 0 and maximum of 255.
1832#[derive(Debug, PartialEq, Eq, Serialize, Default, Clone, Copy)]
1833#[cfg_attr(feature = "schema", derive(JsonSchema), schemars(with = "InnerColor"))]
1834#[serde(rename_all = "camelCase", deny_unknown_fields)]
1835pub struct Color(pub u8, pub u8, pub u8, pub u8);
1836
1837impl From<Color> for (u8, u8, u8, u8) {
1838  fn from(value: Color) -> Self {
1839    (value.0, value.1, value.2, value.3)
1840  }
1841}
1842
1843impl From<Color> for (u8, u8, u8) {
1844  fn from(value: Color) -> Self {
1845    (value.0, value.1, value.2)
1846  }
1847}
1848
1849impl From<(u8, u8, u8, u8)> for Color {
1850  fn from(value: (u8, u8, u8, u8)) -> Self {
1851    Color(value.0, value.1, value.2, value.3)
1852  }
1853}
1854
1855impl From<(u8, u8, u8)> for Color {
1856  fn from(value: (u8, u8, u8)) -> Self {
1857    Color(value.0, value.1, value.2, 255)
1858  }
1859}
1860
1861impl From<Color> for [u8; 4] {
1862  fn from(value: Color) -> Self {
1863    [value.0, value.1, value.2, value.3]
1864  }
1865}
1866
1867impl From<Color> for [u8; 3] {
1868  fn from(value: Color) -> Self {
1869    [value.0, value.1, value.2]
1870  }
1871}
1872
1873impl From<[u8; 4]> for Color {
1874  fn from(value: [u8; 4]) -> Self {
1875    Color(value[0], value[1], value[2], value[3])
1876  }
1877}
1878
1879impl From<[u8; 3]> for Color {
1880  fn from(value: [u8; 3]) -> Self {
1881    Color(value[0], value[1], value[2], 255)
1882  }
1883}
1884
1885impl FromStr for Color {
1886  type Err = String;
1887  fn from_str(mut color: &str) -> Result<Self, Self::Err> {
1888    color = color.trim().strip_prefix('#').unwrap_or(color);
1889    let color = match color.len() {
1890      3 => color.chars()
1891            .flat_map(|c| std::iter::repeat_n(c, 2))
1892            .chain(std::iter::repeat_n('f', 2))
1893            .collect(),
1894      6 => format!("{color}FF"),
1895      8 => color.to_string(),
1896      _ => return Err("Invalid hex color length, must be either 3, 6 or 8, for example: #fff, #ffffff, or #ffffffff".into()),
1897    };
1898
1899    let r = u8::from_str_radix(&color[0..2], 16).map_err(|e| e.to_string())?;
1900    let g = u8::from_str_radix(&color[2..4], 16).map_err(|e| e.to_string())?;
1901    let b = u8::from_str_radix(&color[4..6], 16).map_err(|e| e.to_string())?;
1902    let a = u8::from_str_radix(&color[6..8], 16).map_err(|e| e.to_string())?;
1903
1904    Ok(Color(r, g, b, a))
1905  }
1906}
1907
1908fn default_alpha() -> u8 {
1909  255
1910}
1911
1912#[derive(Deserialize)]
1913#[cfg_attr(feature = "schema", derive(JsonSchema))]
1914#[serde(untagged)]
1915enum InnerColor {
1916  /// Color hex string, for example: #fff, #ffffff, or #ffffffff.
1917  String(
1918    #[cfg_attr(
1919      feature = "schema",
1920      schemars(pattern("^#?([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$"))
1921    )]
1922    String,
1923  ),
1924  /// Array of RGB colors. Each value has minimum of 0 and maximum of 255.
1925  Rgb((u8, u8, u8)),
1926  /// Array of RGBA colors. Each value has minimum of 0 and maximum of 255.
1927  Rgba((u8, u8, u8, u8)),
1928  /// Object of red, green, blue, alpha color values. Each value has minimum of 0 and maximum of 255.
1929  RgbaObject {
1930    red: u8,
1931    green: u8,
1932    blue: u8,
1933    #[serde(default = "default_alpha")]
1934    alpha: u8,
1935  },
1936}
1937
1938impl<'de> Deserialize<'de> for Color {
1939  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1940  where
1941    D: Deserializer<'de>,
1942  {
1943    let color = InnerColor::deserialize(deserializer)?;
1944    let color = match color {
1945      InnerColor::String(string) => string.parse().map_err(serde::de::Error::custom)?,
1946      InnerColor::Rgb(rgb) => Color(rgb.0, rgb.1, rgb.2, 255),
1947      InnerColor::Rgba(rgb) => rgb.into(),
1948      InnerColor::RgbaObject {
1949        red,
1950        green,
1951        blue,
1952        alpha,
1953      } => Color(red, green, blue, alpha),
1954    };
1955
1956    Ok(color)
1957  }
1958}
1959
1960/// Background throttling policy.
1961#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1962#[cfg_attr(feature = "schema", derive(JsonSchema))]
1963#[serde(rename_all = "camelCase", deny_unknown_fields)]
1964pub enum BackgroundThrottlingPolicy {
1965  /// A policy where background throttling is disabled
1966  Disabled,
1967  /// A policy where a web view that's not in a window fully suspends tasks. This is usually the default behavior in case no policy is set.
1968  Suspend,
1969  /// A policy where a web view that's not in a window limits processing, but does not fully suspend tasks.
1970  Throttle,
1971}
1972
1973/// The window effects configuration object
1974#[skip_serializing_none]
1975#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default)]
1976#[cfg_attr(feature = "schema", derive(JsonSchema))]
1977#[serde(rename_all = "camelCase", deny_unknown_fields)]
1978pub struct WindowEffectsConfig {
1979  /// List of Window effects to apply to the Window.
1980  ///
1981  /// Generally, conflicting effects will apply the first one and ignore the rest but
1982  /// on macOS you can specify one Liquid Glass style and one Visual Effect material at the same time
1983  /// to make Tauri fallback to the latter on macOS 15 and below.
1984  pub effects: Vec<WindowEffect>,
1985  /// Window effect state **macOS Only**. Ignored for Liquid Glass Effects.
1986  pub state: Option<WindowEffectState>,
1987  /// Window effect corner radius **macOS Only**
1988  pub radius: Option<f64>,
1989  /// Window effect color.
1990  ///
1991  /// ## Platform-specific
1992  ///
1993  /// - **Windows**: Affects [`WindowEffect::Blur`] and [`WindowEffect::Acrylic`] only
1994  /// on Windows 10 v1903+. Doesn't have any effect on Windows 7 or Windows 11.
1995  /// - **macOS**: Only affects Liquid Glass effects.
1996  pub color: Option<Color>,
1997  /// Enables interactive glass behavior, which adds a visual response to user interactions.
1998  ///
1999  /// **macOS 27.0+**. Only affects Liquid Glass effects.
2000  #[serde(default)]
2001  pub interactive: bool,
2002}
2003
2004/// Enable prevent overflow with a margin
2005/// so that the window's size + this margin won't overflow the workarea
2006#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default)]
2007#[cfg_attr(feature = "schema", derive(JsonSchema))]
2008#[serde(rename_all = "camelCase", deny_unknown_fields)]
2009pub struct PreventOverflowMargin {
2010  /// Horizontal margin in physical pixels
2011  pub width: u32,
2012  /// Vertical margin in physical pixels
2013  pub height: u32,
2014}
2015
2016/// Prevent overflow with a margin
2017#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
2018#[cfg_attr(feature = "schema", derive(JsonSchema))]
2019#[serde(untagged)]
2020pub enum PreventOverflowConfig {
2021  /// Enable prevent overflow or not
2022  Enable(bool),
2023  /// Enable prevent overflow with a margin
2024  /// so that the window's size + this margin won't overflow the workarea
2025  Margin(PreventOverflowMargin),
2026}
2027
2028/// The scrollbar style to use in the webview.
2029///
2030/// ## Platform-specific
2031///
2032/// - **Windows**: This option must be given the same value for all webviews that target the same data directory.
2033#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Default)]
2034#[cfg_attr(feature = "schema", derive(JsonSchema))]
2035#[serde(rename_all = "camelCase", deny_unknown_fields)]
2036#[non_exhaustive]
2037pub enum ScrollBarStyle {
2038  #[default]
2039  /// The platform's native scrollbar, as rendered by the webview by default.
2040  ///
2041  /// This is the only supported value outside of Windows.
2042  Default,
2043
2044  /// Fluent UI style overlay scrollbars. **Windows Only**
2045  ///
2046  /// Requires WebView2 Runtime version 125.0.2535.41 or higher, does nothing on older versions,
2047  /// see <https://learn.microsoft.com/en-us/microsoft-edge/webview2/release-notes/?tabs=dotnetcsharp#10253541>
2048  FluentOverlay,
2049}
2050
2051/// The window configuration object.
2052///
2053/// See more: <https://v2.tauri.app/reference/config/#windowconfig>
2054#[skip_serializing_none]
2055#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
2056#[cfg_attr(feature = "schema", derive(JsonSchema))]
2057#[serde(rename_all = "camelCase", deny_unknown_fields)]
2058pub struct WindowConfig {
2059  /// The window identifier. It must be alphanumeric.
2060  #[serde(default = "default_window_label")]
2061  pub label: String,
2062  /// Whether Tauri should create this window at app startup or not.
2063  ///
2064  /// When this is set to `false` you must manually grab the config object via `app.config().app.windows`
2065  /// and create it with [`WebviewWindowBuilder::from_config`](https://docs.rs/tauri/2/tauri/webview/struct.WebviewWindowBuilder.html#method.from_config).
2066  ///
2067  /// ## Example:
2068  ///
2069  /// ```rust,no_run
2070  /// tauri::Builder::default()
2071  ///   .setup(|app| {
2072  ///     tauri::WebviewWindowBuilder::from_config(app.handle(), &app.config().app.windows[0])?.build()?;
2073  ///     Ok(())
2074  ///   });
2075  /// ```
2076  #[serde(default = "default_true")]
2077  pub create: bool,
2078  /// The window webview URL.
2079  #[serde(default)]
2080  pub url: WebviewUrl,
2081  /// The user agent for the webview
2082  #[serde(alias = "user-agent")]
2083  pub user_agent: Option<String>,
2084  /// Whether the drag and drop handlers used internally to generate [`DragDropEvent`]s are enabled on the webview. By default it is enabled.
2085  ///
2086  /// Disabling it is required to use HTML5 drag and drop on the frontend on Windows since we replace the drag drop handler of WebView2.
2087  ///
2088  /// Note: this setting maps to [`WebviewBuilder::disable_drag_drop_handler`], not [`WindowBuilder::drag_and_drop`].
2089  ///
2090  /// [`DragDropEvent`]: https://docs.rs/tauri/latest/tauri/enum.DragDropEvent.html
2091  /// [`WebviewBuilder::disable_drag_drop_handler`]: https://docs.rs/tauri/latest/tauri/webview/struct.WebviewBuilder.html#method.disable_drag_drop_handler
2092  /// [`WindowBuilder::drag_and_drop`]: https://docs.rs/tauri/latest/x86_64-pc-windows-msvc/tauri/window/struct.WindowBuilder.html#method.drag_and_drop
2093  #[serde(default = "default_true", alias = "drag-drop-enabled")]
2094  pub drag_drop_enabled: bool,
2095  /// Whether or not the window starts centered or not.
2096  #[serde(default)]
2097  pub center: bool,
2098  /// The horizontal position of the window's top left corner in logical pixels
2099  pub x: Option<f64>,
2100  /// The vertical position of the window's top left corner in logical pixels
2101  pub y: Option<f64>,
2102  /// The window width in logical pixels.
2103  #[serde(default = "default_width")]
2104  pub width: f64,
2105  /// The window height in logical pixels.
2106  #[serde(default = "default_height")]
2107  pub height: f64,
2108  /// The min window width in logical pixels.
2109  #[serde(alias = "min-width")]
2110  pub min_width: Option<f64>,
2111  /// The min window height in logical pixels.
2112  #[serde(alias = "min-height")]
2113  pub min_height: Option<f64>,
2114  /// The max window width in logical pixels.
2115  #[serde(alias = "max-width")]
2116  pub max_width: Option<f64>,
2117  /// The max window height in logical pixels.
2118  #[serde(alias = "max-height")]
2119  pub max_height: Option<f64>,
2120  /// Whether or not to prevent the window from overflowing the workarea
2121  ///
2122  /// ## Platform-specific
2123  ///
2124  /// - **iOS / Android:** Unsupported.
2125  #[serde(alias = "prevent-overflow")]
2126  pub prevent_overflow: Option<PreventOverflowConfig>,
2127  /// Whether the window is resizable or not. When resizable is set to false, native window's maximize button is automatically disabled.
2128  #[serde(default = "default_true")]
2129  pub resizable: bool,
2130  /// Whether the window's native maximize button is enabled or not.
2131  /// If resizable is set to false, this setting is ignored.
2132  ///
2133  /// ## Platform-specific
2134  ///
2135  /// - **macOS:** Disables the "zoom" button in the window titlebar, which is also used to enter fullscreen mode.
2136  /// - **Linux / iOS / Android:** Unsupported.
2137  #[serde(default = "default_true")]
2138  pub maximizable: bool,
2139  /// Whether the window's native minimize button is enabled or not.
2140  ///
2141  /// ## Platform-specific
2142  ///
2143  /// - **Linux / iOS / Android:** Unsupported.
2144  #[serde(default = "default_true")]
2145  pub minimizable: bool,
2146  /// Whether the window's native close button is enabled or not.
2147  ///
2148  /// ## Platform-specific
2149  ///
2150  /// - **Linux:** "GTK+ will do its best to convince the window manager not to show a close button.
2151  ///   Depending on the system, this function may not have any effect when called on a window that is already visible"
2152  /// - **iOS / Android:** Unsupported.
2153  #[serde(default = "default_true")]
2154  pub closable: bool,
2155  /// The window title.
2156  #[serde(default = "default_title")]
2157  pub title: String,
2158  /// Whether the window starts as fullscreen or not.
2159  #[serde(default)]
2160  pub fullscreen: bool,
2161  /// Whether the window will be initially focused or not.
2162  #[serde(default = "default_true")]
2163  pub focus: bool,
2164  /// Whether the window will be focusable or not.
2165  #[serde(default = "default_true")]
2166  pub focusable: bool,
2167  /// Whether the window is transparent or not.
2168  ///
2169  /// ## Platform-specific
2170  ///
2171  /// - **Windows**: Using `noRedirectionBitmap` can help avoid a white flash when creating a transparent window.
2172  /// - **CEF runtime**: The window can be transparent but the webview cannot: a windowed Chromium browser paints an opaque background. The runtime logs a warning.
2173  #[serde(default)]
2174  pub transparent: bool,
2175  /// Whether the window is maximized or not.
2176  #[serde(default)]
2177  pub maximized: bool,
2178  /// Whether the window is visible or not.
2179  #[serde(default = "default_true")]
2180  pub visible: bool,
2181  /// Whether the window should have borders and bars.
2182  #[serde(default = "default_true")]
2183  pub decorations: bool,
2184  /// Whether the window should always be below other windows.
2185  #[serde(default, alias = "always-on-bottom")]
2186  pub always_on_bottom: bool,
2187  /// Whether the window should always be on top of other windows.
2188  #[serde(default, alias = "always-on-top")]
2189  pub always_on_top: bool,
2190  /// Whether the window should be visible on all workspaces or virtual desktops.
2191  ///
2192  /// ## Platform-specific
2193  ///
2194  /// - **Windows / iOS / Android:** Unsupported.
2195  #[serde(default, alias = "visible-on-all-workspaces")]
2196  pub visible_on_all_workspaces: bool,
2197  /// Prevents the window contents from being captured by other apps.
2198  #[serde(default, alias = "content-protected")]
2199  pub content_protected: bool,
2200  /// If `true`, hides the window icon from the taskbar on Windows and Linux.
2201  #[serde(default, alias = "skip-taskbar")]
2202  pub skip_taskbar: bool,
2203  /// The name of the window class created on Windows to create the window. **Windows only**.
2204  pub window_classname: Option<String>,
2205  /// This sets `WS_EX_NOREDIRECTIONBITMAP`.
2206  ///
2207  /// This can avoid the white flash that may appear before the webview content is rendered
2208  /// when using a transparent window. **Windows only**.
2209  #[serde(default, alias = "no-redirection-bitmap")]
2210  pub no_redirection_bitmap: bool,
2211  /// The initial window theme. Defaults to the system theme. Only implemented on Windows and macOS 10.14+.
2212  pub theme: Option<crate::Theme>,
2213  /// The style of the macOS title bar.
2214  #[serde(default, alias = "title-bar-style")]
2215  pub title_bar_style: TitleBarStyle,
2216  /// The position of the window controls on macOS.
2217  ///
2218  /// Requires titleBarStyle: Overlay and decorations: true.
2219  #[serde(default, alias = "traffic-light-position")]
2220  pub traffic_light_position: Option<LogicalPosition>,
2221  /// If `true`, sets the window title to be hidden on macOS.
2222  #[serde(default, alias = "hidden-title")]
2223  pub hidden_title: bool,
2224  /// Whether clicking an inactive window also clicks through to the webview on macOS.
2225  ///
2226  /// ## Platform-specific
2227  ///
2228  /// - **CEF runtime:** Unsupported. Chromium decides on its own whether the click that activates
2229  ///   the window reaches the page: it is swallowed on regular windows and only clicks through on
2230  ///   always-on-top windows or while a DevTools debugger is attached.
2231  #[serde(default, alias = "accept-first-mouse")]
2232  pub accept_first_mouse: bool,
2233  /// Defines the window [tabbing identifier] for macOS.
2234  ///
2235  /// Windows with matching tabbing identifiers will be grouped together.
2236  /// If the tabbing identifier is not set, automatic tabbing will be disabled.
2237  ///
2238  /// [tabbing identifier]: <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
2239  #[serde(default, alias = "tabbing-identifier")]
2240  pub tabbing_identifier: Option<String>,
2241  /// Defines additional browser arguments on Windows.
2242  ///
2243  /// ## Platform-specific
2244  ///
2245  /// - **CEF runtime**: Unsupported. Chromium's command line is per process, not per webview;
2246  ///   pass switches through `Cef::command_line_arg` in Rust instead.
2247  ///
2248  /// ## Warning
2249  ///
2250  /// Webview instances with different browser arguments must also have different [data directories](Self::data_directory).
2251  ///
2252  /// By default wry passes `--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection`
2253  /// so if you set this, you also need to disable these components by yourself if you want.
2254  #[serde(default, alias = "additional-browser-args")]
2255  pub additional_browser_args: Option<String>,
2256  /// Whether or not the window has shadow.
2257  ///
2258  /// ## Platform-specific
2259  ///
2260  /// - **Windows:**
2261  ///   - `false` has no effect on decorated window, shadow are always ON.
2262  ///   - `true` will make undecorated window have a 1px white border,
2263  /// and on Windows 11, it will have a rounded corners.
2264  /// - **Linux:** Unsupported.
2265  #[serde(default = "default_true")]
2266  pub shadow: bool,
2267  /// Window effects.
2268  ///
2269  /// Requires the window to be transparent.
2270  ///
2271  /// ## Platform-specific:
2272  ///
2273  /// - **Windows**: If using decorations or shadows, you may want to try this workaround <https://github.com/tauri-apps/tao/issues/72#issuecomment-975607891>
2274  /// - **Linux**: Unsupported
2275  #[serde(default, alias = "window-effects")]
2276  pub window_effects: Option<WindowEffectsConfig>,
2277  /// Whether or not the webview should be launched in incognito  mode.
2278  ///
2279  /// ## Platform-specific:
2280  ///
2281  /// - **Android**: Unsupported.
2282  #[serde(default)]
2283  pub incognito: bool,
2284  /// Sets the window associated with this label to be the parent of the window to be created.
2285  ///
2286  /// ## Platform-specific
2287  ///
2288  /// - **Windows**: This sets the passed parent as an owner window to the window to be created.
2289  ///   From [MSDN owned windows docs](https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows):
2290  ///     - An owned window is always above its owner in the z-order.
2291  ///     - The system automatically destroys an owned window when its owner is destroyed.
2292  ///     - An owned window is hidden when its owner is minimized.
2293  /// - **Linux**: This makes the new window transient for parent, see <https://docs.gtk.org/gtk3/method.Window.set_transient_for.html>
2294  /// - **macOS**: This adds the window as a child of parent, see <https://developer.apple.com/documentation/appkit/nswindow/1419152-addchildwindow?language=objc>
2295  pub parent: Option<String>,
2296  /// The proxy URL for the WebView for all network requests.
2297  ///
2298  /// Must be either a `http://` or a `socks5://` URL.
2299  ///
2300  /// ## Platform-specific
2301  ///
2302  /// - **macOS**: Requires the `macos-proxy` feature flag and only compiles for macOS 14+.
2303  #[serde(alias = "proxy-url")]
2304  pub proxy_url: Option<Url>,
2305  /// Whether page zooming by hotkeys is enabled
2306  ///
2307  /// ## Platform-specific:
2308  ///
2309  /// - **Windows**: Controls WebView2's [`IsZoomControlEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2settings?view=webview2-winrt-1.0.2420.47#iszoomcontrolenabled) setting.
2310  /// - **MacOS / Linux**: Injects a polyfill that zooms in and out with `ctrl/command` + `-/=`,
2311  /// 20% in each step, ranging from 20% to 1000%. Requires `webview:allow-set-webview-zoom` permission
2312  ///
2313  /// - **Android / iOS**: Unsupported.
2314  #[serde(default, alias = "zoom-hotkeys-enabled")]
2315  pub zoom_hotkeys_enabled: bool,
2316  /// Whether browser extensions can be installed for the webview process
2317  ///
2318  /// ## Platform-specific:
2319  ///
2320  /// - **Windows**: Enables the WebView2 environment's [`AreBrowserExtensionsEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2environmentoptions?view=webview2-winrt-1.0.2739.15#arebrowserextensionsenabled)
2321  /// - **MacOS / Linux / iOS / Android** - Unsupported.
2322  /// - **CEF runtime**: Unsupported. CEF removed its extension loading API; the runtime logs a warning.
2323  #[serde(default, alias = "browser-extensions-enabled")]
2324  pub browser_extensions_enabled: bool,
2325
2326  /// Sets whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android. Defaults to `false`.
2327  ///
2328  /// ## Note
2329  ///
2330  /// Using a `https` scheme will NOT allow mixed content when trying to fetch `http` endpoints and therefore will not match the behavior of the `<scheme>://localhost` protocols used on macOS and Linux.
2331  ///
2332  /// ## Warning
2333  ///
2334  /// Changing this value between releases will change the IndexedDB, cookies and localstorage location and your app will not be able to access the old data.
2335  #[serde(default, alias = "use-https-scheme")]
2336  pub use_https_scheme: bool,
2337  /// Enable web inspector which is usually called browser devtools. Enabled by default.
2338  ///
2339  /// This API works in **debug** builds, but requires `devtools` feature flag to enable it in **release** builds.
2340  ///
2341  /// ## Platform-specific
2342  ///
2343  /// - macOS: This will call private functions on **macOS**.
2344  /// - Android: Open `chrome://inspect/#devices` in Chrome to get the devtools window. Wry's `WebView` devtools API isn't supported on Android.
2345  /// - iOS: Open Safari > Develop > [Your Device Name] > [Your WebView] to get the devtools window.
2346  pub devtools: Option<bool>,
2347
2348  /// Set the window and webview background color.
2349  ///
2350  /// ## Platform-specific:
2351  ///
2352  /// - **Windows**: alpha channel is ignored for the window layer.
2353  /// - **Windows**: On Windows 7, alpha channel is ignored for the webview layer.
2354  /// - **Windows**: On Windows 8 and newer, if alpha channel is not `0`, it will be ignored for the webview layer.
2355  #[serde(alias = "background-color")]
2356  pub background_color: Option<Color>,
2357
2358  /// Change the default background throttling behaviour.
2359  ///
2360  /// By default, browsers use a suspend policy that will throttle timers and even unload
2361  /// the whole tab (view) to free resources after roughly 5 minutes when a view became
2362  /// minimized or hidden. This will pause all tasks until the documents visibility state
2363  /// changes back from hidden to visible by bringing the view back to the foreground.
2364  ///
2365  /// ## Platform-specific
2366  ///
2367  /// - **Linux / Windows / Android**: Unsupported. Workarounds like a pending WebLock transaction might suffice.
2368  /// - **iOS**: Supported since version 17.0+.
2369  /// - **macOS**: Supported since version 14.0+.
2370  /// - **CEF runtime**: Unsupported per webview. Chromium throttles hidden pages process-wide; pass `--disable-background-timer-throttling` through `Cef::command_line_arg` to turn that off for every webview.
2371  ///
2372  /// see <https://github.com/tauri-apps/tauri/issues/5250#issuecomment-2569380578>
2373  #[serde(default, alias = "background-throttling")]
2374  pub background_throttling: Option<BackgroundThrottlingPolicy>,
2375  /// Whether we should disable JavaScript code execution on the webview or not.
2376  #[serde(default, alias = "javascript-disabled")]
2377  pub javascript_disabled: bool,
2378  /// on macOS and iOS there is a link preview on long pressing links, this is enabled by default.
2379  /// see https://docs.rs/objc2-web-kit/latest/objc2_web_kit/struct.WKWebView.html#method.allowsLinkPreview
2380  ///
2381  /// Not applicable on the CEF runtime, Chromium has no link previews.
2382  #[serde(default = "default_true", alias = "allow-link-preview")]
2383  pub allow_link_preview: bool,
2384  /// Allows disabling the input accessory view on iOS.
2385  ///
2386  /// The accessory view is the view that appears above the keyboard when a text input element is focused.
2387  /// It usually displays a view with "Done", "Next" buttons.
2388  #[serde(
2389    default,
2390    alias = "disable-input-accessory-view",
2391    alias = "disable_input_accessory_view"
2392  )]
2393  pub disable_input_accessory_view: bool,
2394  /// Set a custom path for the webview's data directory (localStorage, cache, etc.),
2395  /// **relative to the local data directory (`localDataDir()`), followed by the window label**.
2396  ///
2397  /// To set absolute paths, use [`WebviewWindowBuilder::data_directory`](https://docs.rs/tauri/2/tauri/webview/struct.WebviewWindowBuilder.html#method.data_directory)
2398  ///
2399  /// This path is not affected by the `app > appDirectoriesOverride` config.
2400  /// To keep the webview data in an overridden directory, leave this unset (the webview then uses the app local data directory)
2401  /// or resolve a path from `app.path().app_local_data_dir()` and set it with `WebviewWindowBuilder::data_directory`.
2402  ///
2403  /// #### Platform-specific:
2404  ///
2405  /// - **Windows**: WebViews with different values for settings like `additionalBrowserArgs`, `browserExtensionsEnabled` or `scrollBarStyle` must have different data directories.
2406  /// - **macOS / iOS**: Unsupported, use `dataStoreIdentifier` instead.
2407  /// - **Android**: Unsupported.
2408  #[serde(default, alias = "data-directory")]
2409  pub data_directory: Option<PathBuf>,
2410  /// Initialize the WebView with a custom data store identifier. This can be seen as a replacement for `dataDirectory` which is unavailable in WKWebView.
2411  ///
2412  /// See <https://developer.apple.com/documentation/webkit/wkwebsitedatastore/init(foridentifier:)?language=objc>
2413  ///
2414  /// The array must contain 16 u8 numbers.
2415  ///
2416  /// #### Platform-specific:
2417  ///
2418  /// - **iOS**: Supported since version 17.0+.
2419  /// - **macOS**: Supported since version 14.0+.
2420  /// - **Windows / Linux / Android**: Unsupported.
2421  /// - **CEF runtime**: Supported. The identifier names a profile directory under the runtime's cache path, the same isolation `dataDirectory` gives; `dataDirectory` wins when both are set.
2422  #[serde(default, alias = "data-store-identifier")]
2423  pub data_store_identifier: Option<[u8; 16]>,
2424
2425  /// Specifies the native scrollbar style to use with the webview.
2426  /// CSS styles that modify the scrollbar are applied on top of the native appearance configured here.
2427  ///
2428  /// Defaults to `default`, which is the browser default.
2429  ///
2430  /// ## Platform-specific
2431  ///
2432  /// - **Windows**:
2433  ///   - `fluentOverlay` requires WebView2 Runtime version 125.0.2535.41 or higher,
2434  ///     and does nothing on older versions.
2435  ///   - This option must be given the same value for all webviews that target the same data directory.
2436  /// - **Linux / Android / iOS / macOS**: Unsupported. Only supports `Default` and performs no operation.
2437  /// - **CEF runtime**: Unsupported per webview. Overlay scrollbars are a process-wide Chromium feature; enable them for every webview with `Cef::enable_features(["OverlayScrollbar"])`.
2438  #[serde(default, alias = "scroll-bar-style")]
2439  pub scroll_bar_style: ScrollBarStyle,
2440
2441  /// Whether to limit navigations to App-Bound Domains.
2442  ///
2443  /// This is required to enable Service Workers in WKWebView, which are otherwise
2444  /// unavailable. Defaults to `false`.
2445  ///
2446  /// When this is set to `true`, the webview can only navigate to the domains listed in the
2447  /// `WKAppBoundDomains` array of `src-tauri/Info.ios.plist`. Add `localhost` and every
2448  /// [registrable domain](https://developer.mozilla.org/en-US/docs/Glossary/Registrable_domain)
2449  /// this webview loads to that array:
2450  ///
2451  /// ```xml
2452  /// <plist>
2453  /// <dict>
2454  ///     <key>WKAppBoundDomains</key>
2455  ///     <array>
2456  ///         <string>localhost</string>
2457  ///         <string>aregistrabledomain.example</string>
2458  ///     </array>
2459  /// </dict>
2460  /// </plist>
2461  /// ```
2462  ///
2463  /// `localhost` must be listed if any webview with this option enabled opens a local webpage,
2464  /// makes any localhost call, or uses the isolation pattern, because Tauri serves the
2465  /// application webpage, the IPC protocol and the isolation pattern iframe from the
2466  /// `localhost` domain.
2467  ///
2468  /// Requests served through custom URI schemes are allowed as long as they use a registrable
2469  /// domain listed in the `WKAppBoundDomains` array, including requests to the `localhost`
2470  /// domain.
2471  ///
2472  /// An entire URI scheme can be listed by adding the protocol name followed by a colon, for
2473  /// example `stream:` for a custom `stream` scheme (see the
2474  /// [streaming example](https://github.com/tauri-apps/tauri/blob/dev/examples/streaming/main.rs)).
2475  /// This is not covered by Apple's
2476  /// [App-Bound Domains announcement](https://webkit.org/blog/10882/app-bound-domains/),
2477  /// so it may not be accepted during App Store review.
2478  ///
2479  /// See <https://webkit.org/blog/10882/app-bound-domains/> and
2480  /// <https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/limitsnavigationstoappbounddomains>
2481  /// for the official documentation on App-Bound Domains.
2482  ///
2483  /// ## Platform-specific
2484  ///
2485  /// - **iOS**: Supported since version 14.0+.
2486  /// - **Linux / Windows / Android / macOS:** Unsupported.
2487  #[serde(default, alias = "limit-navigations-to-app-bound-domains")]
2488  pub limit_navigations_to_app_bound_domains: bool,
2489  /// The name of the Android activity to create for this window.
2490  #[serde(default, alias = "activity-name")]
2491  pub activity_name: Option<String>,
2492  /// The name of the Android activity that is creating this webview window.
2493  ///
2494  /// This is important to determine which stack the activity will belong to.
2495  #[serde(default, alias = "created-by-activity-name")]
2496  pub created_by_activity_name: Option<String>,
2497
2498  /// Sets the identifier of the scene that is requesting the new scene,
2499  /// establishing a relationship between the two scenes.
2500  ///
2501  /// By default the system uses the foreground scene.
2502  #[serde(default, alias = "requested-by-scene-identifier")]
2503  pub requested_by_scene_identifier: Option<String>,
2504  /// Controls the WebView's browser-level general autofill behavior.
2505  ///
2506  /// **This option does not disable password or credit card autofill.**
2507  ///
2508  /// When set to `false`, the WebView will not automatically populate
2509  /// general form fields using previously stored data such as addresses
2510  /// or contact information.
2511  ///
2512  /// If not specified, this is `true` by default.
2513  ///
2514  /// ## Platform-specific
2515  ///
2516  /// - **Windows**: Supported. WebView2's autofill feature (called
2517  ///   "Suggestions") may not honor `autocomplete="off"` on input
2518  ///   elements in some cases.
2519  /// - **Linux / Android / iOS / macOS**: Unsupported and performs no
2520  ///   operation.
2521  /// - **CEF runtime**: Autofill is already off on this runtime (it disables `autofill.profile_enabled` on every profile), so `false` is the state you get; turn it on with `Cef::profile_preference("autofill.profile_enabled", true)`.
2522  #[serde(default = "default_true", alias = "general-autofill-enabled")]
2523  pub general_autofill_enabled: bool,
2524}
2525
2526impl Default for WindowConfig {
2527  fn default() -> Self {
2528    Self {
2529      label: default_window_label(),
2530      url: WebviewUrl::default(),
2531      create: true,
2532      user_agent: None,
2533      drag_drop_enabled: true,
2534      center: false,
2535      x: None,
2536      y: None,
2537      width: default_width(),
2538      height: default_height(),
2539      min_width: None,
2540      min_height: None,
2541      max_width: None,
2542      max_height: None,
2543      prevent_overflow: None,
2544      resizable: true,
2545      maximizable: true,
2546      minimizable: true,
2547      closable: true,
2548      title: default_title(),
2549      fullscreen: false,
2550      focus: true,
2551      focusable: true,
2552      transparent: false,
2553      maximized: false,
2554      visible: true,
2555      decorations: true,
2556      always_on_bottom: false,
2557      always_on_top: false,
2558      visible_on_all_workspaces: false,
2559      content_protected: false,
2560      skip_taskbar: false,
2561      window_classname: None,
2562      no_redirection_bitmap: false,
2563      theme: None,
2564      title_bar_style: Default::default(),
2565      traffic_light_position: None,
2566      hidden_title: false,
2567      accept_first_mouse: false,
2568      tabbing_identifier: None,
2569      additional_browser_args: None,
2570      shadow: true,
2571      window_effects: None,
2572      incognito: false,
2573      parent: None,
2574      proxy_url: None,
2575      zoom_hotkeys_enabled: false,
2576      browser_extensions_enabled: false,
2577      use_https_scheme: false,
2578      devtools: None,
2579      background_color: None,
2580      background_throttling: None,
2581      javascript_disabled: false,
2582      allow_link_preview: true,
2583      disable_input_accessory_view: false,
2584      data_directory: None,
2585      data_store_identifier: None,
2586      scroll_bar_style: ScrollBarStyle::Default,
2587      limit_navigations_to_app_bound_domains: false,
2588      activity_name: None,
2589      created_by_activity_name: None,
2590      requested_by_scene_identifier: None,
2591      general_autofill_enabled: true,
2592    }
2593  }
2594}
2595
2596fn default_window_label() -> String {
2597  "main".to_string()
2598}
2599
2600fn default_width() -> f64 {
2601  800.
2602}
2603
2604fn default_height() -> f64 {
2605  600.
2606}
2607
2608fn default_title() -> String {
2609  "Tauri App".to_string()
2610}
2611
2612/// A Content-Security-Policy directive source list.
2613/// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/Sources#sources>.
2614#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2615#[cfg_attr(feature = "schema", derive(JsonSchema))]
2616#[serde(rename_all = "camelCase", untagged)]
2617pub enum CspDirectiveSources {
2618  /// An inline list of CSP sources. Same as [`Self::List`], but concatenated with a space separator.
2619  Inline(String),
2620  /// A list of CSP sources. The collection will be concatenated with a space separator for the CSP string.
2621  List(Vec<String>),
2622}
2623
2624impl Default for CspDirectiveSources {
2625  fn default() -> Self {
2626    Self::List(Vec::new())
2627  }
2628}
2629
2630impl From<CspDirectiveSources> for Vec<String> {
2631  fn from(sources: CspDirectiveSources) -> Self {
2632    match sources {
2633      CspDirectiveSources::Inline(source) => source.split(' ').map(|s| s.to_string()).collect(),
2634      CspDirectiveSources::List(l) => l,
2635    }
2636  }
2637}
2638
2639impl CspDirectiveSources {
2640  /// Whether the given source is configured on this directive or not.
2641  pub fn contains(&self, source: &str) -> bool {
2642    match self {
2643      Self::Inline(s) => s.contains(&format!("{source} ")) || s.contains(&format!(" {source}")),
2644      Self::List(l) => l.contains(&source.into()),
2645    }
2646  }
2647
2648  /// Appends the given source to this directive.
2649  pub fn push<S: AsRef<str>>(&mut self, source: S) {
2650    match self {
2651      Self::Inline(s) => {
2652        s.push(' ');
2653        s.push_str(source.as_ref());
2654      }
2655      Self::List(l) => {
2656        l.push(source.as_ref().to_string());
2657      }
2658    }
2659  }
2660
2661  /// Extends this CSP directive source list with the given array of sources.
2662  pub fn extend(&mut self, sources: Vec<String>) {
2663    for s in sources {
2664      self.push(s);
2665    }
2666  }
2667}
2668
2669/// A Content-Security-Policy definition.
2670/// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.
2671#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
2672#[cfg_attr(feature = "schema", derive(JsonSchema))]
2673#[serde(rename_all = "camelCase", untagged)]
2674pub enum Csp {
2675  /// The entire CSP policy in a single text string.
2676  Policy(String),
2677  /// An object mapping a directive with its sources values as a list of strings.
2678  DirectiveMap(HashMap<String, CspDirectiveSources>),
2679}
2680
2681impl Serialize for Csp {
2682  fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
2683  where
2684    S: Serializer,
2685  {
2686    match self {
2687      Self::Policy(policy) => serializer.serialize_str(policy),
2688      Self::DirectiveMap(map) => {
2689        // Serialize through `BTreeMap` so the output is deterministic
2690        // see: https://github.com/tauri-apps/tauri/issues/14978
2691        // TODO: Remove this in v3, use a BTreeMap instead of a HashMap
2692        let btree_map: BTreeMap<_, _> = map.iter().collect();
2693        btree_map.serialize(serializer)
2694      }
2695    }
2696  }
2697}
2698
2699impl From<HashMap<String, CspDirectiveSources>> for Csp {
2700  fn from(map: HashMap<String, CspDirectiveSources>) -> Self {
2701    Self::DirectiveMap(map)
2702  }
2703}
2704
2705impl From<Csp> for HashMap<String, CspDirectiveSources> {
2706  fn from(csp: Csp) -> Self {
2707    match csp {
2708      Csp::Policy(policy) => {
2709        let mut map = HashMap::new();
2710        for directive in policy.split(';') {
2711          let mut tokens = directive.trim().split(' ');
2712          if let Some(directive) = tokens.next() {
2713            let sources = tokens.map(|s| s.to_string()).collect::<Vec<String>>();
2714            map.insert(directive.to_string(), CspDirectiveSources::List(sources));
2715          }
2716        }
2717        map
2718      }
2719      Csp::DirectiveMap(m) => m,
2720    }
2721  }
2722}
2723
2724impl Display for Csp {
2725  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2726    match self {
2727      Self::Policy(s) => write!(f, "{s}"),
2728      Self::DirectiveMap(m) => {
2729        let len = m.len();
2730        let mut i = 0;
2731        for (directive, sources) in m {
2732          let sources: Vec<String> = sources.clone().into();
2733          write!(f, "{} {}", directive, sources.join(" "))?;
2734          i += 1;
2735          if i != len {
2736            write!(f, "; ")?;
2737          }
2738        }
2739        Ok(())
2740      }
2741    }
2742  }
2743}
2744
2745/// The possible values for the `dangerous_disable_asset_csp_modification` config option.
2746#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2747#[serde(untagged)]
2748#[cfg_attr(feature = "schema", derive(JsonSchema))]
2749pub enum DisabledCspModificationKind {
2750  /// If `true`, disables all CSP modification.
2751  /// `false` is the default value and it configures Tauri to control the CSP.
2752  Flag(bool),
2753  /// Disables the given list of CSP directives modifications.
2754  List(Vec<String>),
2755}
2756
2757impl DisabledCspModificationKind {
2758  /// Determines whether the given CSP directive can be modified or not.
2759  pub fn can_modify(&self, directive: &str) -> bool {
2760    match self {
2761      Self::Flag(f) => !f,
2762      Self::List(l) => !l.contains(&directive.into()),
2763    }
2764  }
2765}
2766
2767impl Default for DisabledCspModificationKind {
2768  fn default() -> Self {
2769    Self::Flag(false)
2770  }
2771}
2772
2773/// Protocol scope definition.
2774/// It is a list of glob patterns that restrict the API access from the webview.
2775///
2776/// Each pattern can start with a variable that resolves to a system base directory.
2777/// The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`,
2778/// `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`,
2779/// `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$TEMP`,
2780/// `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.
2781#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2782#[serde(untagged)]
2783#[cfg_attr(feature = "schema", derive(JsonSchema))]
2784pub enum FsScope {
2785  /// A list of paths that are allowed by this scope.
2786  AllowedPaths(Vec<PathBuf>),
2787  /// A complete scope configuration.
2788  #[serde(rename_all = "camelCase")]
2789  Scope {
2790    /// A list of paths that are allowed by this scope.
2791    #[serde(default)]
2792    allow: Vec<PathBuf>,
2793    /// A list of paths that are not allowed by this scope.
2794    /// This gets precedence over the [`Self::Scope::allow`] list.
2795    #[serde(default)]
2796    deny: Vec<PathBuf>,
2797    /// Whether or not paths that contain components that start with a `.`
2798    /// will require that `.` appears literally in the pattern; `*`, `?`, `**`,
2799    /// or `[...]` will not match. This is useful because such files are
2800    /// conventionally considered hidden on Unix systems and it might be
2801    /// desirable to skip them when listing files.
2802    ///
2803    /// Defaults to `true` on Unix systems and `false` on Windows
2804    // dotfiles are not supposed to be exposed by default on unix
2805    #[serde(alias = "require-literal-leading-dot")]
2806    require_literal_leading_dot: Option<bool>,
2807  },
2808}
2809
2810impl Default for FsScope {
2811  fn default() -> Self {
2812    Self::AllowedPaths(Vec::new())
2813  }
2814}
2815
2816impl FsScope {
2817  /// The list of allowed paths.
2818  pub fn allowed_paths(&self) -> &Vec<PathBuf> {
2819    match self {
2820      Self::AllowedPaths(p) => p,
2821      Self::Scope { allow, .. } => allow,
2822    }
2823  }
2824
2825  /// The list of forbidden paths.
2826  pub fn forbidden_paths(&self) -> Option<&Vec<PathBuf>> {
2827    match self {
2828      Self::AllowedPaths(_) => None,
2829      Self::Scope { deny, .. } => Some(deny),
2830    }
2831  }
2832}
2833
2834/// Config for the asset custom protocol.
2835///
2836/// See more: <https://v2.tauri.app/reference/config/#assetprotocolconfig>
2837#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2838#[cfg_attr(feature = "schema", derive(JsonSchema))]
2839#[serde(rename_all = "camelCase", deny_unknown_fields)]
2840pub struct AssetProtocolConfig {
2841  /// The access scope for the asset protocol.
2842  #[serde(default)]
2843  pub scope: FsScope,
2844  /// Enables the asset protocol.
2845  #[serde(default)]
2846  pub enable: bool,
2847}
2848
2849/// definition of a header source
2850///
2851/// The header value to a header name
2852#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
2853#[cfg_attr(feature = "schema", derive(JsonSchema))]
2854#[serde(rename_all = "camelCase", untagged)]
2855pub enum HeaderSource {
2856  /// string version of the header Value
2857  Inline(String),
2858  /// list version of the header value. Item are joined by "," for the real header value
2859  List(Vec<String>),
2860  /// (Rust struct | Json | JavaScript Object) equivalent of the header value. Items are composed from: key + space + value. Item are then joined by ";" for the real header value
2861  Map(HashMap<String, String>),
2862}
2863
2864impl Serialize for HeaderSource {
2865  fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
2866  where
2867    S: Serializer,
2868  {
2869    match self {
2870      Self::Inline(s) => serializer.serialize_str(s),
2871      Self::List(l) => l.serialize(serializer),
2872      Self::Map(m) => {
2873        // Serialize through `BTreeMap` so the output is deterministic
2874        // see: https://github.com/tauri-apps/tauri/issues/14978
2875        // TODO: Remove this in v3, use a BTreeMap instead of a HashMap
2876        let btree_map: BTreeMap<_, _> = m.iter().collect();
2877        btree_map.serialize(serializer)
2878      }
2879    }
2880  }
2881}
2882
2883impl Display for HeaderSource {
2884  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2885    match self {
2886      Self::Inline(s) => write!(f, "{s}"),
2887      Self::List(l) => write!(f, "{}", l.join(", ")),
2888      Self::Map(m) => {
2889        // Format through `BTreeMap` so the resulting header value is deterministic
2890        // see: https://github.com/tauri-apps/tauri/issues/14978
2891        // TODO: Remove this in v3, use a BTreeMap instead of a HashMap
2892        let map: BTreeMap<_, _> = m.iter().collect();
2893        let len = map.len();
2894        for (i, (key, value)) in map.into_iter().enumerate() {
2895          write!(f, "{key} {value}")?;
2896          if i + 1 != len {
2897            write!(f, "; ")?;
2898          }
2899        }
2900        Ok(())
2901      }
2902    }
2903  }
2904}
2905
2906/// A trait which implements on the [`Builder`] of the http create
2907///
2908/// Must add headers defined in the tauri configuration file to http responses
2909pub trait HeaderAddition {
2910  /// adds all headers defined on the config file, given the current HeaderConfig
2911  fn add_configured_headers(self, headers: Option<&HeaderConfig>) -> http::response::Builder;
2912}
2913
2914impl HeaderAddition for http::response::Builder {
2915  /// Add the headers defined in the tauri configuration file to http responses
2916  ///
2917  /// this is a utility function, which is used in the same way as the `.header(..)` of the rust http library
2918  fn add_configured_headers(mut self, headers: Option<&HeaderConfig>) -> http::response::Builder {
2919    if let Some(headers) = headers {
2920      // Add the header Access-Control-Allow-Credentials, if we find a value for it
2921      if let Some(value) = &headers.access_control_allow_credentials {
2922        self = self.header("Access-Control-Allow-Credentials", value.to_string());
2923      };
2924
2925      // Add the header Access-Control-Allow-Headers, if we find a value for it
2926      if let Some(value) = &headers.access_control_allow_headers {
2927        self = self.header("Access-Control-Allow-Headers", value.to_string());
2928      };
2929
2930      // Add the header Access-Control-Allow-Methods, if we find a value for it
2931      if let Some(value) = &headers.access_control_allow_methods {
2932        self = self.header("Access-Control-Allow-Methods", value.to_string());
2933      };
2934
2935      // Add the header Access-Control-Expose-Headers, if we find a value for it
2936      if let Some(value) = &headers.access_control_expose_headers {
2937        self = self.header("Access-Control-Expose-Headers", value.to_string());
2938      };
2939
2940      // Add the header Access-Control-Max-Age, if we find a value for it
2941      if let Some(value) = &headers.access_control_max_age {
2942        self = self.header("Access-Control-Max-Age", value.to_string());
2943      };
2944
2945      // Add the header Cross-Origin-Embedder-Policy, if we find a value for it
2946      if let Some(value) = &headers.cross_origin_embedder_policy {
2947        self = self.header("Cross-Origin-Embedder-Policy", value.to_string());
2948      };
2949
2950      // Add the header Cross-Origin-Opener-Policy, if we find a value for it
2951      if let Some(value) = &headers.cross_origin_opener_policy {
2952        self = self.header("Cross-Origin-Opener-Policy", value.to_string());
2953      };
2954
2955      // Add the header Cross-Origin-Resource-Policy, if we find a value for it
2956      if let Some(value) = &headers.cross_origin_resource_policy {
2957        self = self.header("Cross-Origin-Resource-Policy", value.to_string());
2958      };
2959
2960      // Add the header Permissions-Policy, if we find a value for it
2961      if let Some(value) = &headers.permissions_policy {
2962        self = self.header("Permissions-Policy", value.to_string());
2963      };
2964
2965      if let Some(value) = &headers.service_worker_allowed {
2966        self = self.header("Service-Worker-Allowed", value.to_string());
2967      }
2968
2969      // Add the header Timing-Allow-Origin, if we find a value for it
2970      if let Some(value) = &headers.timing_allow_origin {
2971        self = self.header("Timing-Allow-Origin", value.to_string());
2972      };
2973
2974      // Add the header X-Content-Type-Options, if we find a value for it
2975      if let Some(value) = &headers.x_content_type_options {
2976        self = self.header("X-Content-Type-Options", value.to_string());
2977      };
2978
2979      // Add the header Tauri-Custom-Header, if we find a value for it
2980      if let Some(value) = &headers.tauri_custom_header {
2981        // Keep in mind to correctly set the Access-Control-Expose-Headers
2982        self = self.header("Tauri-Custom-Header", value.to_string());
2983      };
2984    }
2985    self
2986  }
2987}
2988
2989/// A struct, where the keys are some specific http header names.
2990///
2991/// If the values to those keys are defined, then they will be send as part of a response message.
2992/// This does not include error messages and ipc messages
2993///
2994/// ## Example configuration
2995/// ```javascript
2996/// {
2997///  //..
2998///   app:{
2999///     //..
3000///     security: {
3001///       headers: {
3002///         "Cross-Origin-Opener-Policy": "same-origin",
3003///         "Cross-Origin-Embedder-Policy": "require-corp",
3004///         "Timing-Allow-Origin": [
3005///           "https://developer.mozilla.org",
3006///           "https://example.com",
3007///         ],
3008///         "Access-Control-Expose-Headers": "Tauri-Custom-Header",
3009///         "Tauri-Custom-Header": {
3010///           "key1": "'value1' 'value2'",
3011///           "key2": "'value3'"
3012///         }
3013///       },
3014///       csp: "default-src 'self'; connect-src ipc: http://ipc.localhost",
3015///     }
3016///     //..
3017///   }
3018///  //..
3019/// }
3020/// ```
3021/// In this example `Cross-Origin-Opener-Policy` and `Cross-Origin-Embedder-Policy` are set to allow for the use of [`SharedArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer).
3022/// The result is, that those headers are then set on every response sent via the `get_response` function in crates/tauri/src/protocol/tauri.rs.
3023/// The Content-Security-Policy header is defined separately, because it is also handled separately.
3024///
3025/// For the helloworld example, this config translates into those response headers:
3026/// ```http
3027/// access-control-allow-origin:  http://tauri.localhost
3028/// access-control-expose-headers: Tauri-Custom-Header
3029/// content-security-policy: default-src 'self'; connect-src ipc: http://ipc.localhost; script-src 'self' 'sha256-Wjjrs6qinmnr+tOry8x8PPwI77eGpUFR3EEGZktjJNs='
3030/// content-type: text/html
3031/// cross-origin-embedder-policy: require-corp
3032/// cross-origin-opener-policy: same-origin
3033/// tauri-custom-header: key1 'value1' 'value2'; key2 'value3'
3034/// timing-allow-origin: https://developer.mozilla.org, https://example.com
3035/// ```
3036/// Since the resulting header values are always 'string-like'. So depending on the what data type the HeaderSource is, they need to be converted.
3037///  - `String`(JS/Rust): stay the same for the resulting header value
3038///  - `Array`(JS)/`Vec\<String\>`(Rust): Item are joined by ", " for the resulting header value
3039///  - `Object`(JS)/ `Hashmap\<String,String\>`(Rust): Items are composed from: key + space + value. Item are then joined by "; " for the resulting header value
3040#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
3041#[cfg_attr(feature = "schema", derive(JsonSchema))]
3042#[serde(deny_unknown_fields)]
3043pub struct HeaderConfig {
3044  /// The Access-Control-Allow-Credentials response header tells browsers whether the
3045  /// server allows cross-origin HTTP requests to include credentials.
3046  ///
3047  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials>
3048  #[serde(rename = "Access-Control-Allow-Credentials")]
3049  pub access_control_allow_credentials: Option<HeaderSource>,
3050  /// The Access-Control-Allow-Headers response header is used in response
3051  /// to a preflight request which includes the Access-Control-Request-Headers
3052  /// to indicate which HTTP headers can be used during the actual request.
3053  ///
3054  /// This header is required if the request has an Access-Control-Request-Headers header.
3055  ///
3056  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers>
3057  #[serde(rename = "Access-Control-Allow-Headers")]
3058  pub access_control_allow_headers: Option<HeaderSource>,
3059  /// The Access-Control-Allow-Methods response header specifies one or more methods
3060  /// allowed when accessing a resource in response to a preflight request.
3061  ///
3062  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods>
3063  #[serde(rename = "Access-Control-Allow-Methods")]
3064  pub access_control_allow_methods: Option<HeaderSource>,
3065  /// The Access-Control-Expose-Headers response header allows a server to indicate
3066  /// which response headers should be made available to scripts running in the browser,
3067  /// in response to a cross-origin request.
3068  ///
3069  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers>
3070  #[serde(rename = "Access-Control-Expose-Headers")]
3071  pub access_control_expose_headers: Option<HeaderSource>,
3072  /// The Access-Control-Max-Age response header indicates how long the results of a
3073  /// preflight request (that is the information contained in the
3074  /// Access-Control-Allow-Methods and Access-Control-Allow-Headers headers) can
3075  /// be cached.
3076  ///
3077  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age>
3078  #[serde(rename = "Access-Control-Max-Age")]
3079  pub access_control_max_age: Option<HeaderSource>,
3080  /// The HTTP Cross-Origin-Embedder-Policy (COEP) response header configures embedding
3081  /// cross-origin resources into the document.
3082  ///
3083  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy>
3084  #[serde(rename = "Cross-Origin-Embedder-Policy")]
3085  pub cross_origin_embedder_policy: Option<HeaderSource>,
3086  /// The HTTP Cross-Origin-Opener-Policy (COOP) response header allows you to ensure a
3087  /// top-level document does not share a browsing context group with cross-origin documents.
3088  /// COOP will process-isolate your document and potential attackers can't access your global
3089  /// object if they were to open it in a popup, preventing a set of cross-origin attacks dubbed XS-Leaks.
3090  ///
3091  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy>
3092  #[serde(rename = "Cross-Origin-Opener-Policy")]
3093  pub cross_origin_opener_policy: Option<HeaderSource>,
3094  /// The HTTP Cross-Origin-Resource-Policy response header conveys a desire that the
3095  /// browser blocks no-cors cross-origin/cross-site requests to the given resource.
3096  ///
3097  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy>
3098  #[serde(rename = "Cross-Origin-Resource-Policy")]
3099  pub cross_origin_resource_policy: Option<HeaderSource>,
3100  /// The HTTP Permissions-Policy header provides a mechanism to allow and deny the
3101  /// use of browser features in a document or within any \<iframe\> elements in the document.
3102  ///
3103  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy>
3104  #[serde(rename = "Permissions-Policy")]
3105  pub permissions_policy: Option<HeaderSource>,
3106  /// The HTTP Service-Worker-Allowed response header is used to broaden the path restriction for a
3107  /// service worker's default scope.
3108  ///
3109  /// By default, the scope for a service worker registration is the directory where the service
3110  /// worker script is located. For example, if the script `sw.js` is located in `/js/sw.js`,
3111  /// it can only control URLs under `/js/` by default. Servers can use the `Service-Worker-Allowed`
3112  /// header to allow a service worker to control URLs outside of its own directory.
3113  ///
3114  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Service-Worker-Allowed>
3115  #[serde(rename = "Service-Worker-Allowed")]
3116  pub service_worker_allowed: Option<HeaderSource>,
3117  /// The Timing-Allow-Origin response header specifies origins that are allowed to see values
3118  /// of attributes retrieved via features of the Resource Timing API, which would otherwise be
3119  /// reported as zero due to cross-origin restrictions.
3120  ///
3121  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Timing-Allow-Origin>
3122  #[serde(rename = "Timing-Allow-Origin")]
3123  pub timing_allow_origin: Option<HeaderSource>,
3124  /// The X-Content-Type-Options response HTTP header is a marker used by the server to indicate
3125  /// that the MIME types advertised in the Content-Type headers should be followed and not be
3126  /// changed. The header allows you to avoid MIME type sniffing by saying that the MIME types
3127  /// are deliberately configured.
3128  ///
3129  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options>
3130  #[serde(rename = "X-Content-Type-Options")]
3131  pub x_content_type_options: Option<HeaderSource>,
3132  /// A custom header field Tauri-Custom-Header, don't use it.
3133  /// Remember to set Access-Control-Expose-Headers accordingly
3134  ///
3135  /// **NOT INTENDED FOR PRODUCTION USE**
3136  #[serde(rename = "Tauri-Custom-Header")]
3137  pub tauri_custom_header: Option<HeaderSource>,
3138}
3139
3140impl HeaderConfig {
3141  /// creates a new header config
3142  pub fn new() -> Self {
3143    HeaderConfig {
3144      access_control_allow_credentials: None,
3145      access_control_allow_methods: None,
3146      access_control_allow_headers: None,
3147      access_control_expose_headers: None,
3148      access_control_max_age: None,
3149      cross_origin_embedder_policy: None,
3150      cross_origin_opener_policy: None,
3151      cross_origin_resource_policy: None,
3152      permissions_policy: None,
3153      service_worker_allowed: None,
3154      timing_allow_origin: None,
3155      x_content_type_options: None,
3156      tauri_custom_header: None,
3157    }
3158  }
3159}
3160
3161/// Security configuration.
3162///
3163/// See more: <https://v2.tauri.app/reference/config/#securityconfig>
3164#[skip_serializing_none]
3165#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
3166#[cfg_attr(feature = "schema", derive(JsonSchema))]
3167#[serde(rename_all = "camelCase", deny_unknown_fields)]
3168pub struct SecurityConfig {
3169  /// The Content Security Policy that will be injected on all HTML files on the built application.
3170  /// If `devCsp` is not specified, this value is also injected on dev.
3171  ///
3172  /// This is a really important part of the configuration since it helps you ensure your WebView is secured.
3173  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.
3174  pub csp: Option<Csp>,
3175  /// The Content Security Policy that will be injected on all HTML files on development.
3176  ///
3177  /// This is a really important part of the configuration since it helps you ensure your WebView is secured.
3178  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.
3179  #[serde(alias = "dev-csp")]
3180  pub dev_csp: Option<Csp>,
3181  /// Whether `Object.freeze(Object.prototype)` is run as an initialization script on every webview.
3182  ///
3183  /// This hardens the frontend against prototype pollution: once the prototype is frozen,
3184  /// a script cannot add or replace properties on `Object.prototype` and thus cannot tamper
3185  /// with objects it does not own, including the ones used by the Tauri API.
3186  ///
3187  /// The script runs before any of your frontend code, on every webview, regardless of whether
3188  /// the content is served by the custom protocol or by a development server.
3189  ///
3190  /// Defaults to `false`. Note that frontend libraries that extend built-in prototypes
3191  /// (polyfills, some older frameworks) stop working when this is enabled, so test your
3192  /// application with it on before shipping.
3193  #[serde(default, alias = "freeze-prototype")]
3194  pub freeze_prototype: bool,
3195  /// Disables the Tauri-injected CSP sources.
3196  ///
3197  /// At compile time, Tauri parses all the frontend assets and changes the Content-Security-Policy
3198  /// to only allow loading of your own scripts and styles by injecting nonce and hash sources.
3199  /// This stricts your CSP, which may introduce issues when using along with other flexing sources.
3200  ///
3201  /// This configuration option allows both a boolean and a list of strings as value.
3202  /// A boolean instructs Tauri to disable the injection for all CSP injections,
3203  /// and a list of strings indicates the CSP directives that Tauri cannot inject.
3204  ///
3205  /// **WARNING:** Only disable this if you know what you are doing and have properly configured the CSP.
3206  /// Your application might be vulnerable to XSS attacks without this Tauri protection.
3207  #[serde(default, alias = "dangerous-disable-asset-csp-modification")]
3208  pub dangerous_disable_asset_csp_modification: DisabledCspModificationKind,
3209  /// Custom protocol config.
3210  #[serde(default, alias = "asset-protocol")]
3211  pub asset_protocol: AssetProtocolConfig,
3212  /// The application pattern, which defines how the frontend communicates with the Rust core.
3213  ///
3214  /// - `brownfield` (default): the frontend talks to the core directly. Use it unless you need
3215  ///   the extra isolation layer.
3216  /// - `isolation`: every IPC message is routed through a secure JavaScript application you own,
3217  ///   hosted in a sandboxed `<iframe>`, so it can validate or reject messages before they reach
3218  ///   the Rust core. This protects the core from an untrusted or compromised frontend
3219  ///   (for example one that loads third-party scripts), at the cost of an extra build step:
3220  ///   the `dir` value must point at a directory containing the isolation application's `index.html`.
3221  ///
3222  /// See <https://tauri.app/concept/inter-process-communication/isolation/>.
3223  #[serde(default)]
3224  pub pattern: PatternKind,
3225  /// List of capabilities that are enabled on the application.
3226  ///
3227  /// By default (not set or empty list), all capability files from `./capabilities/` are included,
3228  /// by setting values in this entry, you have fine grained control over which capabilities are included
3229  ///
3230  /// You can either reference a capability file defined in `./capabilities/` with its identifier or inline a [`Capability`]
3231  ///
3232  /// ### Example
3233  ///
3234  /// ```json
3235  /// {
3236  ///   "app": {
3237  ///     "security": {
3238  ///       "capabilities": [
3239  ///         "main-window",
3240  ///         {
3241  ///           "identifier": "drag-window",
3242  ///           "permissions": ["core:window:allow-start-dragging"]
3243  ///         }
3244  ///       ]
3245  ///     }
3246  ///   }
3247  /// }
3248  /// ```
3249  #[serde(default)]
3250  pub capabilities: Vec<CapabilityEntry>,
3251  /// The headers, which are added to every http response from tauri to the web view
3252  /// This doesn't include IPC Messages and error responses
3253  #[serde(default)]
3254  pub headers: Option<HeaderConfig>,
3255}
3256
3257/// A capability entry which can be either an inlined capability or a reference to a capability defined on its own file.
3258#[derive(Debug, Clone, PartialEq, Serialize)]
3259#[cfg_attr(feature = "schema", derive(JsonSchema))]
3260#[serde(untagged)]
3261pub enum CapabilityEntry {
3262  /// An inlined capability.
3263  Inlined(Capability),
3264  /// Reference to a capability identifier.
3265  Reference(String),
3266}
3267
3268impl<'de> Deserialize<'de> for CapabilityEntry {
3269  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3270  where
3271    D: Deserializer<'de>,
3272  {
3273    UntaggedEnumVisitor::new()
3274      .string(|string| Ok(Self::Reference(string.to_owned())))
3275      .map(|map| map.deserialize::<Capability>().map(Self::Inlined))
3276      .deserialize(deserializer)
3277  }
3278}
3279
3280/// The application pattern.
3281#[skip_serializing_none]
3282#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default)]
3283#[serde(rename_all = "lowercase", tag = "use", content = "options")]
3284#[cfg_attr(feature = "schema", derive(JsonSchema))]
3285pub enum PatternKind {
3286  /// Brownfield pattern.
3287  #[default]
3288  Brownfield,
3289  /// Isolation pattern. Recommended for security purposes.
3290  Isolation {
3291    /// The dir containing the index.html file that contains the secure isolation application.
3292    dir: PathBuf,
3293  },
3294}
3295
3296/// The base directory variables an [`AppDirectoriesOverride`] path can start with.
3297///
3298/// `$RESOURCE` is excluded because the resource directory is read-only in bundled apps,
3299/// `$EXE`, `$FONT`, `$RUNTIME` and `$TEMPLATE` because they are not available on every desktop platform,
3300/// and the `$APP*` variables because they refer to the directories being overridden.
3301const APP_DIRECTORIES_OVERRIDE_VARIABLES: &[&str] = &[
3302  "$AUDIO",
3303  "$CACHE",
3304  "$CONFIG",
3305  "$DATA",
3306  "$LOCALDATA",
3307  "$DESKTOP",
3308  "$DOCUMENT",
3309  "$DOWNLOAD",
3310  "$HOME",
3311  "$PICTURE",
3312  "$PUBLIC",
3313  "$TEMP",
3314  "$VIDEO",
3315];
3316
3317/// Validates a path used to override an app directory, see [`AppDirectoriesOverride`].
3318fn validate_app_directory_override(path: &Path) -> Result<(), String> {
3319  let mut components = path.components();
3320  let first = components.next();
3321
3322  if let Some(Component::Normal(first)) = first {
3323    if let Some(variable) = first.to_str().filter(|s| s.starts_with('$')) {
3324      if !APP_DIRECTORIES_OVERRIDE_VARIABLES.contains(&variable) {
3325        return Err(format!(
3326          "`{}` starts with the unsupported base directory variable `{variable}`, expected one of {}",
3327          path.display(),
3328          APP_DIRECTORIES_OVERRIDE_VARIABLES
3329            .iter()
3330            .map(|v| format!("`{v}`"))
3331            .collect::<Vec<_>>()
3332            .join(", ")
3333        ));
3334      }
3335      return Ok(());
3336    }
3337  }
3338
3339  // Windows root-relative (`\foo`) and drive-relative (`C:foo`) paths are neither absolute
3340  // nor relative to the executable, so they cannot be resolved predictably
3341  if !path.is_absolute() && (path.has_root() || matches!(first, Some(Component::Prefix(_)))) {
3342    return Err(format!(
3343      "`{}` must be an absolute path, a path relative to the executable or a path starting with a base directory variable",
3344      path.display()
3345    ));
3346  }
3347
3348  Ok(())
3349}
3350
3351/// Overrides the directories returned by the `app_*_dir` path APIs.
3352///
3353/// See the `app > appDirectoriesOverride` config for how each path is resolved.
3354#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
3355#[cfg_attr(feature = "schema", derive(JsonSchema))]
3356#[serde(untagged)]
3357pub enum AppDirectoriesOverride {
3358  /// A single directory that holds all app directories.
3359  ///
3360  /// The config, data and local data directories resolve to this path,
3361  /// the cache directory resolves to `<path>/caches` and the log directory to `<path>/logs`.
3362  Root(PathBuf),
3363  /// Overrides for individual app directories.
3364  ///
3365  /// Directories that are not listed keep their default location.
3366  Directories(AppDirectoryOverrides),
3367}
3368
3369impl AppDirectoriesOverride {
3370  /// The paths configured by this override.
3371  fn paths(&self) -> impl Iterator<Item = &PathBuf> {
3372    match self {
3373      Self::Root(root) => vec![Some(root)],
3374      Self::Directories(directories) => vec![
3375        directories.config.as_ref(),
3376        directories.data.as_ref(),
3377        directories.local_data.as_ref(),
3378        directories.cache.as_ref(),
3379        directories.log.as_ref(),
3380      ],
3381    }
3382    .into_iter()
3383    .flatten()
3384  }
3385}
3386
3387impl<'de> Deserialize<'de> for AppDirectoriesOverride {
3388  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3389  where
3390    D: Deserializer<'de>,
3391  {
3392    let value = UntaggedEnumVisitor::new()
3393      .string(|path| Ok(Self::Root(PathBuf::from(path))))
3394      .map(|map| {
3395        map
3396          .deserialize::<AppDirectoryOverrides>()
3397          .map(Self::Directories)
3398      })
3399      .deserialize(deserializer)?;
3400
3401    for path in value.paths() {
3402      validate_app_directory_override(path).map_err(DeError::custom)?;
3403    }
3404
3405    Ok(value)
3406  }
3407}
3408
3409/// Overrides for individual app directories.
3410#[skip_serializing_none]
3411#[derive(Debug, Default, PartialEq, Eq, Clone, Serialize, Deserialize)]
3412#[cfg_attr(feature = "schema", derive(JsonSchema))]
3413#[serde(rename_all = "camelCase", deny_unknown_fields)]
3414pub struct AppDirectoryOverrides {
3415  /// Overrides the app config directory (`app_config_dir`, `$APPCONFIG`).
3416  pub config: Option<PathBuf>,
3417  /// Overrides the app data directory (`app_data_dir`, `$APPDATA`).
3418  pub data: Option<PathBuf>,
3419  /// Overrides the app local data directory (`app_local_data_dir`, `$APPLOCALDATA`).
3420  ///
3421  /// On Windows and Linux this is also the default data directory of the webviews.
3422  #[serde(alias = "local-data", alias = "local_data")]
3423  pub local_data: Option<PathBuf>,
3424  /// Overrides the app cache directory (`app_cache_dir`, `$APPCACHE`).
3425  pub cache: Option<PathBuf>,
3426  /// Overrides the app log directory (`app_log_dir`, `$APPLOG`).
3427  pub log: Option<PathBuf>,
3428}
3429
3430/// The App configuration object.
3431///
3432/// See more: <https://v2.tauri.app/reference/config/#appconfig>
3433#[skip_serializing_none]
3434#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
3435#[cfg_attr(feature = "schema", derive(JsonSchema))]
3436#[serde(rename_all = "camelCase", deny_unknown_fields)]
3437pub struct AppConfig {
3438  /// The app windows configuration.
3439  ///
3440  /// ## Example:
3441  ///
3442  /// To create a window at app startup
3443  ///
3444  /// ```json
3445  /// {
3446  ///   "app": {
3447  ///     "windows": [
3448  ///       { "width": 800, "height": 600 }
3449  ///     ]
3450  ///   }
3451  /// }
3452  /// ```
3453  ///
3454  /// If not specified, the window's label (its identifier) defaults to "main",
3455  /// you can use this label to get the window through
3456  /// `app.get_webview_window` in Rust or `WebviewWindow.getByLabel` in JavaScript
3457  ///
3458  /// When working with multiple windows, each window will need an unique label
3459  ///
3460  /// ```json
3461  /// {
3462  ///   "app": {
3463  ///     "windows": [
3464  ///       { "label": "main", "width": 800, "height": 600 },
3465  ///       { "label": "secondary", "width": 800, "height": 600 }
3466  ///     ]
3467  ///   }
3468  /// }
3469  /// ```
3470  ///
3471  /// You can also set `create` to false and use this config through the Rust APIs
3472  ///
3473  /// ```json
3474  /// {
3475  ///   "app": {
3476  ///     "windows": [
3477  ///       { "create": false, "width": 800, "height": 600 }
3478  ///     ]
3479  ///   }
3480  /// }
3481  /// ```
3482  ///
3483  /// and use it like this
3484  ///
3485  /// ```rust,no_run
3486  /// tauri::Builder::default()
3487  ///   .setup(|app| {
3488  ///     tauri::WebviewWindowBuilder::from_config(app.handle(), &app.config().app.windows[0])?.build()?;
3489  ///     Ok(())
3490  ///   });
3491  /// ```
3492  #[serde(default)]
3493  pub windows: Vec<WindowConfig>,
3494  /// Security configuration.
3495  #[serde(default)]
3496  pub security: SecurityConfig,
3497  /// Configuration for app tray icon.
3498  #[serde(alias = "tray-icon")]
3499  pub tray_icon: Option<TrayIconConfig>,
3500  /// Whether we should inject the Tauri API on `window.__TAURI__` or not.
3501  #[serde(default, alias = "with-global-tauri")]
3502  pub with_global_tauri: bool,
3503  /// Whether the application `identifier` is used as the GTK application ID on systems that use GTK.
3504  ///
3505  /// Setting the GTK application ID lets the desktop environment associate the app's windows with
3506  /// its `.desktop` entry of the same name, which is what makes Wayland compositors and GNOME show
3507  /// the correct icon and application name, and group the windows in the dock or taskbar.
3508  ///
3509  /// Defaults to `false`, because registering an application ID also makes GTK register the
3510  /// application on the session bus under that ID, which prevents running more than one instance
3511  /// of the app at the same time.
3512  ///
3513  /// ## Platform-specific
3514  ///
3515  /// - **Linux / FreeBSD / DragonFly / NetBSD / OpenBSD**: The identifier must be a valid GTK
3516  ///   application ID.
3517  /// - **Windows / macOS / Android / iOS**: Unsupported.
3518  #[serde(rename = "enableGTKAppId", alias = "enable-gtk-app-id", default)]
3519  pub enable_gtk_app_id: bool,
3520  /// Overrides the directories returned by the `app_*_dir` path APIs (`app_config_dir`, `app_data_dir`,
3521  /// `app_local_data_dir`, `app_cache_dir` and `app_log_dir`) and the matching `$APPCONFIG`, `$APPDATA`,
3522  /// `$APPLOCALDATA`, `$APPCACHE` and `$APPLOG` base directory variables.
3523  ///
3524  /// This is useful for portable apps that keep all of their data next to the executable,
3525  /// and for apps that want their app directories in a location they choose, such as `$DOCUMENT/my-app`.
3526  /// Everything that resolves paths through these APIs follows the override, including Tauri itself
3527  /// (the default webview data directory on Windows and Linux) and plugins,
3528  /// so the storage locations do not need to be configured one by one.
3529  /// The only exception is a window's `dataDirectory` config, which is not affected.
3530  ///
3531  /// It can also isolate the data of a development build from an installed version of the app,
3532  /// though a distinct `identifier` for development builds achieves that while keeping the production directory layout.
3533  ///
3534  /// The value is either a single path used as the root of every app directory
3535  /// (config, data and local data resolve to the root itself, cache to `<root>/caches` and log to `<root>/logs`),
3536  /// or an object that overrides individual directories (`config`, `data`, `localData`, `cache` and `log`),
3537  /// each resolving to exactly the configured path. Directories that are not listed in the object keep their default location.
3538  ///
3539  /// Each path is resolved as follows:
3540  ///
3541  /// - A path starting with a base directory variable is resolved relative to that directory.
3542  ///   The supported variables are `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`,
3543  ///   `$DOWNLOAD`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$TEMP` and `$VIDEO`.
3544  ///   `..` components are kept, so `$DATA/../my-app` refers to a sibling of the data directory.
3545  /// - An absolute path is used as is.
3546  /// - Any other path is resolved relative to the directory containing the executable,
3547  ///   which must be writable, see the platform-specific notes below.
3548  ///
3549  /// ## Examples
3550  ///
3551  /// Keep all data in an `app-data` folder next to the executable, for a portable build:
3552  ///
3553  /// ```json
3554  /// {
3555  ///   "app": {
3556  ///     "appDirectoriesOverride": "./app-data"
3557  ///   }
3558  /// }
3559  /// ```
3560  ///
3561  /// Only move the logs and the cache:
3562  ///
3563  /// ```json
3564  /// {
3565  ///   "app": {
3566  ///     "appDirectoriesOverride": {
3567  ///       "log": "$DATA/my-app/logs",
3568  ///       "cache": "$CACHE/my-app"
3569  ///     }
3570  ///   }
3571  /// }
3572  /// ```
3573  ///
3574  /// Set the override at runtime, for instance from an environment variable, a command line flag
3575  /// or a directory picked by the user, by modifying the config returned by `tauri::generate_context!()`
3576  /// before building the app:
3577  ///
3578  /// ```rust,no_run
3579  /// use tauri::utils::config::AppDirectoriesOverride;
3580  ///
3581  /// fn main() {
3582  ///   // on an actual app, remove the string argument
3583  ///   let mut context = tauri::generate_context!("../tauri/test/fixture/src-tauri/tauri.conf.json");
3584  ///
3585  ///   if let Ok(data_dir) = std::env::var("MY_APP_DATA_DIR") {
3586  ///     context.config_mut().app.app_directories_override =
3587  ///       Some(AppDirectoriesOverride::Root(data_dir.into()));
3588  ///   }
3589  ///
3590  ///   tauri::Builder::default()
3591  ///     .run(context)
3592  ///     .expect("error while running tauri application");
3593  /// }
3594  /// ```
3595  ///
3596  /// ## Security
3597  ///
3598  /// Scopes and permissions that use the `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE` and `$APPLOG`
3599  /// variables follow the override, so the configured paths must be directories dedicated to the app.
3600  /// A single root is used as is for the config, data and local data directories, so a root that is not dedicated
3601  /// to the app, such as `"./"` or `"$DOCUMENT"`, extends those scopes to everything it contains.
3602  /// With `"./"`, `fs:default` (which allows reading the app directories recursively) lets the webview read
3603  /// every file next to the executable, including anything else in the folder the app was run from,
3604  /// such as the downloads folder. If the app also grants write access to an app directory, a compromised webview
3605  /// (e.g. through XSS) can replace files next to the executable, such as dropping a DLL that Windows loads
3606  /// from the executable's directory on the next launch, leading to code execution.
3607  /// Always point the override to a subfolder owned by the app, such as `"./app-data"` or `"$DOCUMENT/my-app"`.
3608  ///
3609  /// ## Platform-specific
3610  ///
3611  /// A path relative to the executable only works where the executable's directory is writable:
3612  /// portable builds, `tauri dev` builds in the `target` directory and the cases listed below.
3613  /// Everywhere else every write to an app directory fails at runtime, so installed apps should use
3614  /// a base directory variable or an absolute path instead. Unless every distribution of the app is portable,
3615  /// keep relative paths out of the shared configuration and apply them to the portable build flavor only,
3616  /// for instance with the CLI's `--config` flag, which accepts a JSON file or an inline JSON string:
3617  ///
3618  /// ```sh
3619  /// tauri build --config '{ "app": { "appDirectoriesOverride": "./app-data" } }'
3620  /// ```
3621  ///
3622  /// - **Linux**: Relative paths only work for AppImages, where they are resolved relative to the AppImage file,
3623  ///   as long as it is kept in a writable directory. `.deb` and `.rpm` packages install the executable to `/usr/bin`.
3624  /// - **macOS**: Relative paths are resolved next to the `.app` bundle. This does not work for installed apps,
3625  ///   since `/Applications` is not writable for standard users, nor for bundles downloaded from the internet,
3626  ///   which run from a random read-only location (App Translocation) until the user moves them out of the quarantined folder.
3627  /// - **Windows**: Relative paths also work for per-user NSIS installers,
3628  ///   but not for per-machine installers in `Program Files`.
3629  /// - **Android / iOS**: Relative paths are not supported, since there is no writable directory next to the executable.
3630  ///   Use a base directory variable or an absolute path instead. `$DESKTOP` is not available on Android.
3631  #[serde(alias = "app-directories-override")]
3632  pub app_directories_override: Option<AppDirectoriesOverride>,
3633}
3634
3635impl AppConfig {
3636  /// Returns all Cargo features.
3637  pub fn all_features() -> Vec<&'static str> {
3638    vec!["tray-icon", "protocol-asset", "isolation"]
3639  }
3640
3641  /// Returns the enabled Cargo features.
3642  pub fn features(&self) -> Vec<&str> {
3643    let mut features = Vec::new();
3644    if self.tray_icon.is_some() {
3645      features.push("tray-icon");
3646    }
3647    if self.security.asset_protocol.enable {
3648      features.push("protocol-asset");
3649    }
3650
3651    if let PatternKind::Isolation { .. } = self.security.pattern {
3652      features.push("isolation");
3653    }
3654
3655    features.sort_unstable();
3656    features
3657  }
3658}
3659
3660/// Configuration for application tray icon.
3661///
3662/// See more: <https://v2.tauri.app/reference/config/#trayiconconfig>
3663#[skip_serializing_none]
3664#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
3665#[cfg_attr(feature = "schema", derive(JsonSchema))]
3666#[serde(rename_all = "camelCase", deny_unknown_fields)]
3667pub struct TrayIconConfig {
3668  /// Set an id for this tray icon so you can reference it later, defaults to `main`.
3669  pub id: Option<String>,
3670  /// Path to the default icon to use for the tray icon.
3671  ///
3672  /// Note: this stores the image in raw pixels to the final binary,
3673  /// so keep the icon size (width and height) small
3674  /// or else it's going to bloat your final executable
3675  #[serde(alias = "icon-path")]
3676  pub icon_path: PathBuf,
3677  /// A Boolean value that determines whether the image represents a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) image on macOS.
3678  #[serde(default, alias = "icon-as-template")]
3679  pub icon_as_template: bool,
3680  /// **No longer works since v2.2, use [`Self::show_menu_on_left_click`] instead**
3681  ///
3682  /// A Boolean value that determines whether the menu should appear when the tray icon receives a left click.
3683  ///
3684  /// ## Platform-specific:
3685  ///
3686  /// - **Linux**: Unsupported.
3687  #[serde(default = "default_true", alias = "menu-on-left-click")]
3688  #[deprecated(
3689    since = "2.2.0",
3690    note = "No longer works, use `show_menu_on_left_click` instead."
3691  )]
3692  pub menu_on_left_click: bool,
3693  /// A Boolean value that determines whether the menu should appear when the tray icon receives a left click.
3694  ///
3695  /// ## Platform-specific:
3696  ///
3697  /// - **Linux**: Unsupported.
3698  #[serde(default = "default_true", alias = "show-menu-on-left-click")]
3699  pub show_menu_on_left_click: bool,
3700  /// Title for MacOS tray
3701  pub title: Option<String>,
3702  /// Tray icon tooltip on Windows and macOS
3703  pub tooltip: Option<String>,
3704}
3705
3706/// General configuration for the iOS target.
3707#[skip_serializing_none]
3708#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
3709#[cfg_attr(feature = "schema", derive(JsonSchema))]
3710#[serde(rename_all = "camelCase", deny_unknown_fields)]
3711pub struct IosConfig {
3712  /// A custom [XcodeGen] project.yml template to use.
3713  ///
3714  /// [XcodeGen]: <https://github.com/yonaskolb/XcodeGen>
3715  pub template: Option<PathBuf>,
3716  /// A list of strings indicating any iOS frameworks that need to be bundled with the application.
3717  ///
3718  /// Note that you need to recreate the iOS project for the changes to be applied.
3719  pub frameworks: Option<Vec<String>>,
3720  /// The development team. This value is required for iOS development because code signing is enforced.
3721  /// The `APPLE_DEVELOPMENT_TEAM` environment variable can be set to overwrite it.
3722  #[serde(alias = "development-team")]
3723  pub development_team: Option<String>,
3724  /// The version of the build that identifies an iteration of the bundle.
3725  ///
3726  /// Translates to the bundle's CFBundleVersion property.
3727  #[serde(alias = "bundle-version")]
3728  pub bundle_version: Option<String>,
3729  /// A version string indicating the minimum iOS version that the bundled application supports. Defaults to `15.0`.
3730  ///
3731  /// Maps to the IPHONEOS_DEPLOYMENT_TARGET value.
3732  #[serde(
3733    alias = "minimum-system-version",
3734    default = "ios_minimum_system_version"
3735  )]
3736  pub minimum_system_version: String,
3737  /// Path to a Info.plist file to merge with the default Info.plist.
3738  ///
3739  /// Note that Tauri also looks for a `Info.plist` and `Info.ios.plist` file in the same directory as the Tauri configuration file.
3740  #[serde(alias = "info-plist")]
3741  pub info_plist: Option<PathBuf>,
3742}
3743
3744impl Default for IosConfig {
3745  fn default() -> Self {
3746    Self {
3747      template: None,
3748      frameworks: None,
3749      development_team: None,
3750      bundle_version: None,
3751      minimum_system_version: ios_minimum_system_version(),
3752      info_plist: None,
3753    }
3754  }
3755}
3756
3757/// General configuration for the Android target.
3758#[skip_serializing_none]
3759#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
3760#[cfg_attr(feature = "schema", derive(JsonSchema))]
3761#[serde(rename_all = "camelCase", deny_unknown_fields)]
3762pub struct AndroidConfig {
3763  /// The minimum API level required for the application to run.
3764  /// The Android system will prevent the user from installing the application if the system's API level is lower than the value specified.
3765  #[serde(alias = "min-sdk-version", default = "default_min_sdk_version")]
3766  pub min_sdk_version: u32,
3767
3768  /// The version code of the application.
3769  /// It is limited to 2,100,000,000 as per Google Play Store requirements.
3770  ///
3771  /// By default we use your configured version and perform the following math:
3772  /// versionCode = version.major * 1000000 + version.minor * 1000 + version.patch
3773  #[serde(alias = "version-code")]
3774  #[cfg_attr(feature = "schema", validate(range(min = 1, max = 2_100_000_000)))]
3775  pub version_code: Option<u32>,
3776
3777  /// Whether to automatically increment the `versionCode` on each build.
3778  ///
3779  /// - If `true`, the generator will try to read the last `versionCode` from
3780  ///   `tauri.properties` and increment it by 1 for every build.
3781  /// - If `false` or not set, it falls back to `version_code` or semver-derived logic.
3782  ///
3783  /// Note that to use this feature, you should remove `/tauri.properties` from `src-tauri/gen/android/app/.gitignore` so the current versionCode is committed to the repository.
3784  #[serde(alias = "auto-increment-version-code", default)]
3785  pub auto_increment_version_code: bool,
3786
3787  /// Application ID suffix to append for debug builds.
3788  /// This allows installing debug and release versions side-by-side on the same device.
3789  /// Example: ".debug" will make debug builds use "com.example.app.debug" as the application ID.
3790  #[serde(alias = "debug-application-id-suffix")]
3791  pub debug_application_id_suffix: Option<String>,
3792}
3793
3794impl Default for AndroidConfig {
3795  fn default() -> Self {
3796    Self {
3797      min_sdk_version: default_min_sdk_version(),
3798      version_code: None,
3799      auto_increment_version_code: false,
3800      debug_application_id_suffix: None,
3801    }
3802  }
3803}
3804
3805fn default_min_sdk_version() -> u32 {
3806  24
3807}
3808
3809/// Defines the URL or assets to embed in the application.
3810#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
3811#[cfg_attr(feature = "schema", derive(JsonSchema))]
3812#[serde(untagged, deny_unknown_fields)]
3813#[non_exhaustive]
3814pub enum FrontendDist {
3815  /// An external URL that should be used as the default application URL. No assets are embedded in the app in this case.
3816  Url(Url),
3817  /// Path to a directory containing the frontend dist assets.
3818  Directory(PathBuf),
3819  /// An array of files to embed in the app.
3820  Files(Vec<PathBuf>),
3821}
3822
3823impl std::fmt::Display for FrontendDist {
3824  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3825    match self {
3826      Self::Url(url) => write!(f, "{url}"),
3827      Self::Directory(p) => write!(f, "{}", p.display()),
3828      Self::Files(files) => write!(f, "{}", serde_json::to_string(files).unwrap()),
3829    }
3830  }
3831}
3832
3833/// Describes the shell command to run before `tauri dev`.
3834#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
3835#[cfg_attr(feature = "schema", derive(JsonSchema))]
3836#[serde(rename_all = "camelCase", untagged)]
3837pub enum BeforeDevCommand {
3838  /// Run the given script with the default options.
3839  Script(String),
3840  /// Run the given script with custom options.
3841  ScriptWithOptions {
3842    /// The script to execute.
3843    script: String,
3844    /// The current working directory.
3845    cwd: Option<String>,
3846    /// Whether `tauri dev` should wait for the command to finish or not. Defaults to `false`.
3847    #[serde(default)]
3848    wait: bool,
3849  },
3850}
3851
3852/// Describes a shell command to be executed when a CLI hook is triggered.
3853#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
3854#[cfg_attr(feature = "schema", derive(JsonSchema))]
3855#[serde(rename_all = "camelCase", untagged)]
3856pub enum HookCommand {
3857  /// Run the given script with the default options.
3858  Script(String),
3859  /// Run the given script with custom options.
3860  ScriptWithOptions {
3861    /// The script to execute.
3862    script: String,
3863    /// The current working directory.
3864    cwd: Option<String>,
3865  },
3866}
3867
3868/// The runner configuration.
3869#[skip_serializing_none]
3870#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
3871#[cfg_attr(feature = "schema", derive(JsonSchema))]
3872#[serde(untagged)]
3873pub enum RunnerConfig {
3874  /// A string specifying the binary to run.
3875  String(String),
3876  /// An object with advanced configuration options.
3877  Object {
3878    /// The binary to run.
3879    cmd: String,
3880    /// The current working directory to run the command from.
3881    cwd: Option<String>,
3882    /// Arguments to pass to the command.
3883    args: Option<Vec<String>>,
3884  },
3885}
3886
3887impl Default for RunnerConfig {
3888  fn default() -> Self {
3889    RunnerConfig::String("cargo".to_string())
3890  }
3891}
3892
3893impl RunnerConfig {
3894  /// Returns the command to run.
3895  pub fn cmd(&self) -> &str {
3896    match self {
3897      RunnerConfig::String(cmd) => cmd,
3898      RunnerConfig::Object { cmd, .. } => cmd,
3899    }
3900  }
3901
3902  /// Returns the working directory.
3903  pub fn cwd(&self) -> Option<&str> {
3904    match self {
3905      RunnerConfig::String(_) => None,
3906      RunnerConfig::Object { cwd, .. } => cwd.as_deref(),
3907    }
3908  }
3909
3910  /// Returns the arguments.
3911  pub fn args(&self) -> Option<&[String]> {
3912    match self {
3913      RunnerConfig::String(_) => None,
3914      RunnerConfig::Object { args, .. } => args.as_deref(),
3915    }
3916  }
3917}
3918
3919impl std::str::FromStr for RunnerConfig {
3920  type Err = std::convert::Infallible;
3921
3922  fn from_str(s: &str) -> Result<Self, Self::Err> {
3923    Ok(RunnerConfig::String(s.to_string()))
3924  }
3925}
3926
3927impl From<&str> for RunnerConfig {
3928  fn from(s: &str) -> Self {
3929    RunnerConfig::String(s.to_string())
3930  }
3931}
3932
3933impl From<String> for RunnerConfig {
3934  fn from(s: String) -> Self {
3935    RunnerConfig::String(s)
3936  }
3937}
3938
3939/// The Build configuration object.
3940///
3941/// See more: <https://v2.tauri.app/reference/config/#buildconfig>
3942#[skip_serializing_none]
3943#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Default)]
3944#[cfg_attr(feature = "schema", derive(JsonSchema))]
3945#[serde(rename_all = "camelCase", deny_unknown_fields)]
3946pub struct BuildConfig {
3947  /// The binary used to build and run the application.
3948  pub runner: Option<RunnerConfig>,
3949  /// The URL to load in development.
3950  ///
3951  /// This is usually an URL to a dev server, which serves your application assets with hot-reload and HMR.
3952  /// Most modern JavaScript bundlers like [Vite](https://vite.dev/guide/) provides a way to start a dev server by default.
3953  ///
3954  /// If you don't have a dev server or don't want to use one, ignore this option and use [`frontendDist`](BuildConfig::frontend_dist)
3955  /// and point to a web assets directory, and Tauri CLI will run its built-in dev server and provide a simple hot-reload experience.
3956  #[serde(alias = "dev-url")]
3957  pub dev_url: Option<Url>,
3958  /// The path to the application assets (usually the `dist` folder of your javascript bundler)
3959  /// or a URL that could be either a custom protocol registered in the tauri app (for example: `myprotocol://`)
3960  /// or a remote URL (for example: `https://site.com/app`).
3961  ///
3962  /// When a path relative to the configuration file is provided,
3963  /// it is read recursively and all files are embedded in the application binary.
3964  /// Tauri then looks for an `index.html` and serves it as the default entry point for your application.
3965  ///
3966  /// You can also provide a list of paths to be embedded, which allows granular control over what files are added to the binary.
3967  /// In this case, all files are added to the root and you must reference it that way in your HTML files.
3968  ///
3969  /// When a URL is provided, the application won't have bundled assets
3970  /// and the application will load that URL by default.
3971  #[serde(alias = "frontend-dist")]
3972  pub frontend_dist: Option<FrontendDist>,
3973  /// A shell command to run before `tauri dev` kicks in.
3974  ///
3975  /// The `TAURI_ENV_PLATFORM`, `TAURI_ENV_ARCH`, `TAURI_ENV_FAMILY`, `TAURI_ENV_PLATFORM_VERSION`
3976  /// and `TAURI_ENV_TARGET_TRIPLE` environment variables are set for the command, so it can
3977  /// adapt its output to the target that is being built.
3978  /// `TAURI_ENV_DEBUG` is set to `true` for debug builds and is not set otherwise.
3979  #[serde(alias = "before-dev-command")]
3980  pub before_dev_command: Option<BeforeDevCommand>,
3981  /// A shell command to run before `tauri build` kicks in.
3982  ///
3983  /// The `TAURI_ENV_PLATFORM`, `TAURI_ENV_ARCH`, `TAURI_ENV_FAMILY`, `TAURI_ENV_PLATFORM_VERSION`
3984  /// and `TAURI_ENV_TARGET_TRIPLE` environment variables are set for the command, so it can
3985  /// adapt its output to the target that is being built.
3986  /// `TAURI_ENV_DEBUG` is set to `true` for debug builds and is not set otherwise.
3987  #[serde(alias = "before-build-command")]
3988  pub before_build_command: Option<HookCommand>,
3989  /// A shell command to run before the bundling phase in `tauri build` kicks in.
3990  ///
3991  /// The `TAURI_ENV_PLATFORM`, `TAURI_ENV_ARCH`, `TAURI_ENV_FAMILY`, `TAURI_ENV_PLATFORM_VERSION`
3992  /// and `TAURI_ENV_TARGET_TRIPLE` environment variables are set for the command, so it can
3993  /// adapt its output to the target that is being built.
3994  /// `TAURI_ENV_DEBUG` is set to `true` for debug builds and is not set otherwise.
3995  #[serde(alias = "before-bundle-command")]
3996  pub before_bundle_command: Option<HookCommand>,
3997  /// Features passed to `cargo` commands.
3998  pub features: Option<Vec<String>>,
3999  /// Try to remove unused commands registered from plugins base on the ACL list during `tauri build`,
4000  /// the way it works is that tauri-cli will read this and set the environment variables for the build script and macros,
4001  /// and they'll try to get all the allowed commands and remove the rest
4002  ///
4003  /// Note:
4004  ///   - This won't be accounting for dynamically added ACLs when you use features from the `dynamic-acl` (currently enabled by default) feature flag, so make sure to check it when using this
4005  ///   - This feature requires tauri-plugin 2.1 and tauri 2.4
4006  #[serde(alias = "remove-unused-commands", default)]
4007  pub remove_unused_commands: bool,
4008  /// Additional paths to watch for changes when running `tauri dev`.
4009  #[serde(
4010    alias = "additional-watch-folders",
4011    alias = "additional-watch-directories",
4012    default
4013  )]
4014  pub additional_watch_folders: Vec<PathBuf>,
4015  /// Windows-specific build configuration.
4016  #[serde(default)]
4017  pub windows: WindowsBuildConfig,
4018}
4019
4020/// Windows-specific build configuration.
4021#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
4022#[cfg_attr(feature = "schema", derive(JsonSchema))]
4023#[serde(rename_all = "camelCase", deny_unknown_fields)]
4024pub struct WindowsBuildConfig {
4025  /// Whether to statically link the Visual C++ runtime into the application binary on Windows MSVC targets.
4026  #[serde(
4027    default = "default_true",
4028    rename = "staticVCRuntime",
4029    alias = "static-vc-runtime",
4030    alias = "staticVcRuntime"
4031  )]
4032  pub static_vc_runtime: bool,
4033}
4034
4035impl Default for WindowsBuildConfig {
4036  fn default() -> Self {
4037    Self {
4038      static_vc_runtime: true,
4039    }
4040  }
4041}
4042
4043#[derive(Debug, PartialEq, Eq)]
4044struct PackageVersion(String);
4045
4046impl<'d> serde::Deserialize<'d> for PackageVersion {
4047  fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {
4048    struct PackageVersionVisitor;
4049
4050    impl Visitor<'_> for PackageVersionVisitor {
4051      type Value = PackageVersion;
4052
4053      fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
4054        write!(
4055          formatter,
4056          "a semver string or a path to a package.json file"
4057        )
4058      }
4059
4060      fn visit_str<E: DeError>(self, value: &str) -> Result<PackageVersion, E> {
4061        let path = PathBuf::from(value);
4062        if path.exists() {
4063          let json_str = read_to_string(&path)
4064            .map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?;
4065          let package_json: serde_json::Value = serde_json::from_str(&json_str)
4066            .map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?;
4067          if let Some(obj) = package_json.as_object() {
4068            let version = obj
4069              .get("version")
4070              .ok_or_else(|| DeError::custom("JSON must contain a `version` field"))?
4071              .as_str()
4072              .ok_or_else(|| {
4073                DeError::custom(format!("`{} > version` must be a string", path.display()))
4074              })?;
4075            Ok(PackageVersion(
4076              Version::from_str(version)
4077                .map_err(|_| {
4078                  DeError::custom("`tauri.conf.json > version` must be a semver string")
4079                })?
4080                .to_string(),
4081            ))
4082          } else {
4083            Err(DeError::custom(
4084              "`tauri.conf.json > version` value is not a path to a JSON object",
4085            ))
4086          }
4087        } else {
4088          Ok(PackageVersion(
4089            Version::from_str(value)
4090              .map_err(|_| DeError::custom("`tauri.conf.json > version` must be a semver string"))?
4091              .to_string(),
4092          ))
4093        }
4094      }
4095    }
4096
4097    deserializer.deserialize_string(PackageVersionVisitor {})
4098  }
4099}
4100
4101fn version_deserializer<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
4102where
4103  D: Deserializer<'de>,
4104{
4105  Option::<PackageVersion>::deserialize(deserializer).map(|v| v.map(|v| v.0))
4106}
4107
4108/// The Tauri configuration object.
4109/// It is read from a file where you can define your frontend assets,
4110/// configure the bundler and define a tray icon.
4111///
4112/// The configuration file is generated by the
4113/// [`tauri init`](https://v2.tauri.app/reference/cli/#init) command that lives in
4114/// your Tauri application source directory (src-tauri).
4115///
4116/// Once generated, you may modify it at will to customize your Tauri application.
4117///
4118/// ## File Formats
4119///
4120/// By default, the configuration is defined as a JSON file named `tauri.conf.json`.
4121///
4122/// Tauri also supports JSON5 and TOML files via the `config-json5` and `config-toml` Cargo features, respectively.
4123/// The JSON5 file name must be either `tauri.conf.json` or `tauri.conf.json5`.
4124/// The TOML file name is `Tauri.toml`.
4125///
4126/// ## Platform-Specific Configuration
4127///
4128/// In addition to the default configuration file, Tauri can
4129/// read a platform-specific configuration from `tauri.linux.conf.json`,
4130/// `tauri.windows.conf.json`, `tauri.macos.conf.json`, `tauri.android.conf.json` and `tauri.ios.conf.json`
4131/// (or `Tauri.linux.toml`, `Tauri.windows.toml`, `Tauri.macos.toml`, `Tauri.android.toml` and `Tauri.ios.toml` if the `Tauri.toml` format is used),
4132/// which gets merged with the main configuration object.
4133///
4134/// ## Configuration Structure
4135///
4136/// The configuration is composed of the following objects:
4137///
4138/// - [`app`](#appconfig): The Tauri configuration
4139/// - [`build`](#buildconfig): The build configuration
4140/// - [`bundle`](#bundleconfig): The bundle configurations
4141/// - [`plugins`](#pluginconfig): The plugins configuration
4142///
4143/// Example tauri.config.json file:
4144///
4145/// ```json
4146/// {
4147///   "productName": "tauri-app",
4148///   "version": "0.1.0",
4149///   "build": {
4150///     "beforeBuildCommand": "",
4151///     "beforeDevCommand": "",
4152///     "devUrl": "http://localhost:3000",
4153///     "frontendDist": "../dist"
4154///   },
4155///   "app": {
4156///     "security": {
4157///       "csp": null
4158///     },
4159///     "windows": [
4160///       {
4161///         "fullscreen": false,
4162///         "height": 600,
4163///         "resizable": true,
4164///         "title": "Tauri App",
4165///         "width": 800
4166///       }
4167///     ]
4168///   },
4169///   "bundle": {},
4170///   "plugins": {}
4171/// }
4172/// ```
4173#[skip_serializing_none]
4174#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
4175#[cfg_attr(feature = "schema", derive(JsonSchema))]
4176#[serde(rename_all = "camelCase", deny_unknown_fields)]
4177pub struct Config {
4178  /// The JSON schema for the Tauri config.
4179  #[serde(rename = "$schema")]
4180  pub schema: Option<String>,
4181  /// App name.
4182  ///
4183  /// This is the name your app is known by on the user's system, so it must be changed from the
4184  /// default before publishing. Besides naming the generated bundles, it is written into platform
4185  /// metadata and install paths that are expected to be unique to your application.
4186  ///
4187  /// ## Platform-specific
4188  ///
4189  /// - **macOS**: Names the `.app` bundle and the `.dmg`, and sets the bundle's
4190  ///    `CFBundleDisplayName` and `CFBundleName` properties. `CFBundleName` can be overridden with
4191  ///    [`bundle > macOS > bundleName`](MacConfig::bundle_name).
4192  /// - **Linux**: Kebab-cased for the Debian and RPM package names, used as the `Name` entry of
4193  ///    the desktop file and as the resource directory name under `/usr/lib`.
4194  /// - **Windows**: Names the installers, the installation directory, the Start Menu folder and
4195  ///    the `HKCU\Software\<publisher>\<product name>` registry key. It also derives the default
4196  ///    WiX upgrade code, which must be unique across applications and can be set explicitly with
4197  ///    [`bundle > windows > wix > upgradeCode`](WixConfig::upgrade_code).
4198  #[serde(alias = "product-name")]
4199  #[cfg_attr(feature = "schema", schemars(regex(pattern = "^[^/\\:*?\"<>|]+$")))]
4200  pub product_name: Option<String>,
4201  /// Overrides app's main binary filename.
4202  ///
4203  /// By default, Tauri uses the output binary from `cargo`, by setting this, we will rename that binary in `tauri-cli`'s
4204  /// `tauri build` command, and target `tauri bundle` to it
4205  ///
4206  /// If possible, change the [`package name`] or set the [`name field`] instead,
4207  /// and if that's not enough and you're using nightly, consider using the [`different-binary-name`] feature instead
4208  ///
4209  /// Note: this config should not include the binary extension (e.g. `.exe`), we'll add that for you
4210  ///
4211  /// [`package name`]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-name-field
4212  /// [`name field`]: https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-name-field
4213  /// [`different-binary-name`]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#different-binary-name
4214  #[serde(alias = "main-binary-name")]
4215  pub main_binary_name: Option<String>,
4216  /// App version. It is a semver version number or a path to a `package.json` file containing the `version` field.
4217  ///
4218  /// If removed the version number from `Cargo.toml` is used.
4219  /// It's recommended to manage the app versioning in the Tauri config.
4220  ///
4221  /// ## Platform-specific
4222  ///
4223  /// - **macOS**: Translates to the bundle's CFBundleShortVersionString property and is used as the default CFBundleVersion.
4224  ///    You can set an specific bundle version using [`bundle > macOS > bundleVersion`](MacConfig::bundle_version).
4225  /// - **iOS**: Translates to the bundle's CFBundleShortVersionString property and is used as the default CFBundleVersion.
4226  ///    You can set an specific bundle version using [`bundle > iOS > bundleVersion`](IosConfig::bundle_version).
4227  ///    The `tauri ios build` CLI command has a `--build-number <number>` option that lets you append a build number to the app version.
4228  /// - **Android**: By default version 1.0 is used. You can set a version code using [`bundle > android > versionCode`](AndroidConfig::version_code).
4229  ///
4230  /// By default version 1.0 is used on Android.
4231  #[serde(deserialize_with = "version_deserializer", default)]
4232  pub version: Option<String>,
4233  /// The application identifier in reverse domain name notation (e.g. `com.tauri.example`).
4234  /// This string must be unique across applications since it is used in system configurations like
4235  /// the bundle ID and path to the webview data directory.
4236  /// This string must contain only alphanumeric characters (A-Z, a-z, and 0-9), hyphens (-),
4237  /// and periods (.).
4238  /// The default value `com.tauri.dev` is rejected by `tauri build` and must be changed before
4239  /// building your application.
4240  pub identifier: String,
4241  /// The App configuration.
4242  #[serde(default)]
4243  pub app: AppConfig,
4244  /// The build configuration.
4245  #[serde(default)]
4246  pub build: BuildConfig,
4247  /// The bundler configuration.
4248  #[serde(default)]
4249  pub bundle: BundleConfig,
4250  /// The plugins config.
4251  #[serde(default)]
4252  pub plugins: PluginConfig,
4253}
4254
4255/// The plugin configs holds a HashMap mapping a plugin name to its configuration object.
4256///
4257/// See more: <https://v2.tauri.app/reference/config/#pluginconfig>
4258#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize)]
4259#[cfg_attr(feature = "schema", derive(JsonSchema))]
4260pub struct PluginConfig(pub HashMap<String, JsonValue>);
4261
4262impl Serialize for PluginConfig {
4263  fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
4264  where
4265    S: Serializer,
4266  {
4267    // Serialize through `BTreeMap` so the output is deterministic
4268    // see: https://github.com/tauri-apps/tauri/issues/14978
4269    // TODO: Remove this in v3, use a BTreeMap instead of a HashMap
4270    let btree_map: BTreeMap<_, _> = self.0.iter().collect();
4271    btree_map.serialize(serializer)
4272  }
4273}
4274
4275/// Implement `ToTokens` for all config structs, allowing a literal `Config` to be built.
4276///
4277/// This allows for a build script to output the values in a `Config` to a `TokenStream`, which can
4278/// then be consumed by another crate. Useful for passing a config to both the build script and the
4279/// application using tauri while only parsing it once (in the build script).
4280#[cfg(any(feature = "build", feature = "build-2"))]
4281mod build {
4282  use super::*;
4283  use crate::{literal_struct, tokens::*};
4284  use proc_macro2::TokenStream;
4285  use quote::{ToTokens, TokenStreamExt, quote};
4286  use std::convert::identity;
4287
4288  impl ToTokens for WebviewUrl {
4289    fn to_tokens(&self, tokens: &mut TokenStream) {
4290      let prefix = quote! { ::tauri::utils::config::WebviewUrl };
4291
4292      tokens.append_all(match self {
4293        Self::App(path) => {
4294          let path = path_buf_lit(path);
4295          quote! { #prefix::App(#path) }
4296        }
4297        Self::External(url) => {
4298          let url = url_lit(url);
4299          quote! { #prefix::External(#url) }
4300        }
4301        Self::CustomProtocol(url) => {
4302          let url = url_lit(url);
4303          quote! { #prefix::CustomProtocol(#url) }
4304        }
4305      })
4306    }
4307  }
4308
4309  impl ToTokens for BackgroundThrottlingPolicy {
4310    fn to_tokens(&self, tokens: &mut TokenStream) {
4311      let prefix = quote! { ::tauri::utils::config::BackgroundThrottlingPolicy };
4312      tokens.append_all(match self {
4313        Self::Disabled => quote! { #prefix::Disabled },
4314        Self::Throttle => quote! { #prefix::Throttle },
4315        Self::Suspend => quote! { #prefix::Suspend },
4316      })
4317    }
4318  }
4319
4320  impl ToTokens for crate::Theme {
4321    fn to_tokens(&self, tokens: &mut TokenStream) {
4322      let prefix = quote! { ::tauri::utils::Theme };
4323
4324      tokens.append_all(match self {
4325        Self::Light => quote! { #prefix::Light },
4326        Self::Dark => quote! { #prefix::Dark },
4327      })
4328    }
4329  }
4330
4331  impl ToTokens for Color {
4332    fn to_tokens(&self, tokens: &mut TokenStream) {
4333      let Color(r, g, b, a) = self;
4334      tokens.append_all(quote! {::tauri::utils::config::Color(#r,#g,#b,#a)});
4335    }
4336  }
4337  impl ToTokens for WindowEffectsConfig {
4338    fn to_tokens(&self, tokens: &mut TokenStream) {
4339      let effects = vec_lit(self.effects.clone(), |d| d);
4340      let state = opt_lit(self.state.as_ref());
4341      let radius = opt_lit(self.radius.as_ref());
4342      let color = opt_lit(self.color.as_ref());
4343      let interactive = self.interactive;
4344
4345      literal_struct!(
4346        tokens,
4347        ::tauri::utils::config::WindowEffectsConfig,
4348        effects,
4349        state,
4350        radius,
4351        color,
4352        interactive
4353      )
4354    }
4355  }
4356
4357  impl ToTokens for crate::TitleBarStyle {
4358    fn to_tokens(&self, tokens: &mut TokenStream) {
4359      let prefix = quote! { ::tauri::utils::TitleBarStyle };
4360
4361      tokens.append_all(match self {
4362        Self::Visible => quote! { #prefix::Visible },
4363        Self::Transparent => quote! { #prefix::Transparent },
4364        Self::Overlay => quote! { #prefix::Overlay },
4365      })
4366    }
4367  }
4368
4369  impl ToTokens for LogicalPosition {
4370    fn to_tokens(&self, tokens: &mut TokenStream) {
4371      let LogicalPosition { x, y } = self;
4372      literal_struct!(tokens, ::tauri::utils::config::LogicalPosition, x, y)
4373    }
4374  }
4375
4376  impl ToTokens for crate::WindowEffect {
4377    fn to_tokens(&self, tokens: &mut TokenStream) {
4378      let prefix = quote! { ::tauri::utils::WindowEffect };
4379
4380      #[allow(deprecated)]
4381      tokens.append_all(match self {
4382        WindowEffect::AppearanceBased => quote! { #prefix::AppearanceBased},
4383        WindowEffect::Light => quote! { #prefix::Light},
4384        WindowEffect::Dark => quote! { #prefix::Dark},
4385        WindowEffect::MediumLight => quote! { #prefix::MediumLight},
4386        WindowEffect::UltraDark => quote! { #prefix::UltraDark},
4387        WindowEffect::Titlebar => quote! { #prefix::Titlebar},
4388        WindowEffect::Selection => quote! { #prefix::Selection},
4389        WindowEffect::Menu => quote! { #prefix::Menu},
4390        WindowEffect::Popover => quote! { #prefix::Popover},
4391        WindowEffect::Sidebar => quote! { #prefix::Sidebar},
4392        WindowEffect::HeaderView => quote! { #prefix::HeaderView},
4393        WindowEffect::Sheet => quote! { #prefix::Sheet},
4394        WindowEffect::WindowBackground => quote! { #prefix::WindowBackground},
4395        WindowEffect::HudWindow => quote! { #prefix::HudWindow},
4396        WindowEffect::FullScreenUI => quote! { #prefix::FullScreenUI},
4397        WindowEffect::Tooltip => quote! { #prefix::Tooltip},
4398        WindowEffect::ContentBackground => quote! { #prefix::ContentBackground},
4399        WindowEffect::UnderWindowBackground => quote! { #prefix::UnderWindowBackground},
4400        WindowEffect::UnderPageBackground => quote! { #prefix::UnderPageBackground},
4401        WindowEffect::LiquidGlassRegular => quote! { #prefix::LiquidGlassRegular },
4402        WindowEffect::LiquidGlassClear => quote! { #prefix::LiquidGlassClear },
4403        WindowEffect::Mica => quote! { #prefix::Mica},
4404        WindowEffect::MicaDark => quote! { #prefix::MicaDark},
4405        WindowEffect::MicaLight => quote! { #prefix::MicaLight},
4406        WindowEffect::Blur => quote! { #prefix::Blur},
4407        WindowEffect::Acrylic => quote! { #prefix::Acrylic},
4408        WindowEffect::Tabbed => quote! { #prefix::Tabbed },
4409        WindowEffect::TabbedDark => quote! { #prefix::TabbedDark },
4410        WindowEffect::TabbedLight => quote! { #prefix::TabbedLight },
4411      })
4412    }
4413  }
4414
4415  impl ToTokens for crate::WindowEffectState {
4416    fn to_tokens(&self, tokens: &mut TokenStream) {
4417      let prefix = quote! { ::tauri::utils::WindowEffectState };
4418
4419      #[allow(deprecated)]
4420      tokens.append_all(match self {
4421        WindowEffectState::Active => quote! { #prefix::Active},
4422        WindowEffectState::FollowsWindowActiveState => quote! { #prefix::FollowsWindowActiveState},
4423        WindowEffectState::Inactive => quote! { #prefix::Inactive},
4424      })
4425    }
4426  }
4427
4428  impl ToTokens for PreventOverflowMargin {
4429    fn to_tokens(&self, tokens: &mut TokenStream) {
4430      let width = self.width;
4431      let height = self.height;
4432
4433      literal_struct!(
4434        tokens,
4435        ::tauri::utils::config::PreventOverflowMargin,
4436        width,
4437        height
4438      )
4439    }
4440  }
4441
4442  impl ToTokens for PreventOverflowConfig {
4443    fn to_tokens(&self, tokens: &mut TokenStream) {
4444      let prefix = quote! { ::tauri::utils::config::PreventOverflowConfig };
4445
4446      #[allow(deprecated)]
4447      tokens.append_all(match self {
4448        Self::Enable(enable) => quote! { #prefix::Enable(#enable) },
4449        Self::Margin(margin) => quote! { #prefix::Margin(#margin) },
4450      })
4451    }
4452  }
4453
4454  impl ToTokens for ScrollBarStyle {
4455    fn to_tokens(&self, tokens: &mut TokenStream) {
4456      let prefix = quote! { ::tauri::utils::config::ScrollBarStyle };
4457
4458      tokens.append_all(match self {
4459        Self::Default => quote! { #prefix::Default },
4460        Self::FluentOverlay => quote! { #prefix::FluentOverlay },
4461      })
4462    }
4463  }
4464
4465  impl ToTokens for WindowConfig {
4466    fn to_tokens(&self, tokens: &mut TokenStream) {
4467      let label = str_lit(&self.label);
4468      let create = &self.create;
4469      let url = &self.url;
4470      let user_agent = opt_str_lit(self.user_agent.as_ref());
4471      let drag_drop_enabled = self.drag_drop_enabled;
4472      let center = self.center;
4473      let x = opt_lit(self.x.as_ref());
4474      let y = opt_lit(self.y.as_ref());
4475      let width = self.width;
4476      let height = self.height;
4477      let min_width = opt_lit(self.min_width.as_ref());
4478      let min_height = opt_lit(self.min_height.as_ref());
4479      let max_width = opt_lit(self.max_width.as_ref());
4480      let max_height = opt_lit(self.max_height.as_ref());
4481      let prevent_overflow = opt_lit(self.prevent_overflow.as_ref());
4482      let resizable = self.resizable;
4483      let maximizable = self.maximizable;
4484      let minimizable = self.minimizable;
4485      let closable = self.closable;
4486      let title = str_lit(&self.title);
4487      let proxy_url = opt_lit(self.proxy_url.as_ref().map(url_lit).as_ref());
4488      let fullscreen = self.fullscreen;
4489      let focus = self.focus;
4490      let focusable = self.focusable;
4491      let transparent = self.transparent;
4492      let maximized = self.maximized;
4493      let visible = self.visible;
4494      let decorations = self.decorations;
4495      let always_on_bottom = self.always_on_bottom;
4496      let always_on_top = self.always_on_top;
4497      let visible_on_all_workspaces = self.visible_on_all_workspaces;
4498      let content_protected = self.content_protected;
4499      let skip_taskbar = self.skip_taskbar;
4500      let window_classname = opt_str_lit(self.window_classname.as_ref());
4501      let no_redirection_bitmap = self.no_redirection_bitmap;
4502      let theme = opt_lit(self.theme.as_ref());
4503      let title_bar_style = &self.title_bar_style;
4504      let traffic_light_position = opt_lit(self.traffic_light_position.as_ref());
4505      let hidden_title = self.hidden_title;
4506      let accept_first_mouse = self.accept_first_mouse;
4507      let tabbing_identifier = opt_str_lit(self.tabbing_identifier.as_ref());
4508      let additional_browser_args = opt_str_lit(self.additional_browser_args.as_ref());
4509      let shadow = self.shadow;
4510      let window_effects = opt_lit(self.window_effects.as_ref());
4511      let incognito = self.incognito;
4512      let parent = opt_str_lit(self.parent.as_ref());
4513      let zoom_hotkeys_enabled = self.zoom_hotkeys_enabled;
4514      let browser_extensions_enabled = self.browser_extensions_enabled;
4515      let use_https_scheme = self.use_https_scheme;
4516      let devtools = opt_lit(self.devtools.as_ref());
4517      let background_color = opt_lit(self.background_color.as_ref());
4518      let background_throttling = opt_lit(self.background_throttling.as_ref());
4519      let javascript_disabled = self.javascript_disabled;
4520      let allow_link_preview = self.allow_link_preview;
4521      let disable_input_accessory_view = self.disable_input_accessory_view;
4522      let data_directory = opt_lit(self.data_directory.as_ref().map(path_buf_lit).as_ref());
4523      let data_store_identifier = opt_vec_lit(self.data_store_identifier, identity);
4524      let scroll_bar_style = &self.scroll_bar_style;
4525      let limit_navigations_to_app_bound_domains = self.limit_navigations_to_app_bound_domains;
4526      let activity_name = opt_lit(self.activity_name.as_ref());
4527      let created_by_activity_name = opt_lit(self.created_by_activity_name.as_ref());
4528      let requested_by_scene_identifier = opt_lit(self.requested_by_scene_identifier.as_ref());
4529      let general_autofill_enabled = self.general_autofill_enabled;
4530
4531      literal_struct!(
4532        tokens,
4533        ::tauri::utils::config::WindowConfig,
4534        label,
4535        url,
4536        create,
4537        user_agent,
4538        drag_drop_enabled,
4539        center,
4540        x,
4541        y,
4542        width,
4543        height,
4544        min_width,
4545        min_height,
4546        max_width,
4547        max_height,
4548        prevent_overflow,
4549        resizable,
4550        maximizable,
4551        minimizable,
4552        closable,
4553        title,
4554        proxy_url,
4555        fullscreen,
4556        focus,
4557        focusable,
4558        transparent,
4559        maximized,
4560        visible,
4561        decorations,
4562        always_on_bottom,
4563        always_on_top,
4564        visible_on_all_workspaces,
4565        content_protected,
4566        skip_taskbar,
4567        window_classname,
4568        no_redirection_bitmap,
4569        theme,
4570        title_bar_style,
4571        traffic_light_position,
4572        hidden_title,
4573        accept_first_mouse,
4574        tabbing_identifier,
4575        additional_browser_args,
4576        shadow,
4577        window_effects,
4578        incognito,
4579        parent,
4580        zoom_hotkeys_enabled,
4581        browser_extensions_enabled,
4582        use_https_scheme,
4583        devtools,
4584        background_color,
4585        background_throttling,
4586        javascript_disabled,
4587        allow_link_preview,
4588        disable_input_accessory_view,
4589        data_directory,
4590        data_store_identifier,
4591        scroll_bar_style,
4592        limit_navigations_to_app_bound_domains,
4593        activity_name,
4594        created_by_activity_name,
4595        requested_by_scene_identifier,
4596        general_autofill_enabled
4597      );
4598    }
4599  }
4600
4601  impl ToTokens for PatternKind {
4602    fn to_tokens(&self, tokens: &mut TokenStream) {
4603      let prefix = quote! { ::tauri::utils::config::PatternKind };
4604
4605      tokens.append_all(match self {
4606        Self::Brownfield => quote! { #prefix::Brownfield },
4607        #[cfg(not(feature = "isolation"))]
4608        Self::Isolation { dir: _ } => quote! { #prefix::Brownfield },
4609        #[cfg(feature = "isolation")]
4610        Self::Isolation { dir } => {
4611          let dir = path_buf_lit(dir);
4612          quote! { #prefix::Isolation { dir: #dir } }
4613        }
4614      })
4615    }
4616  }
4617
4618  impl ToTokens for WebviewInstallMode {
4619    fn to_tokens(&self, tokens: &mut TokenStream) {
4620      let prefix = quote! { ::tauri::utils::config::WebviewInstallMode };
4621
4622      tokens.append_all(match self {
4623        Self::Skip => quote! { #prefix::Skip },
4624        Self::DownloadBootstrapper { silent } => {
4625          quote! { #prefix::DownloadBootstrapper { silent: #silent } }
4626        }
4627        Self::EmbedBootstrapper { silent } => {
4628          quote! { #prefix::EmbedBootstrapper { silent: #silent } }
4629        }
4630        Self::OfflineInstaller { silent } => {
4631          quote! { #prefix::OfflineInstaller { silent: #silent } }
4632        }
4633        Self::FixedRuntime { path } => {
4634          let path = path_buf_lit(path);
4635          quote! { #prefix::FixedRuntime { path: #path } }
4636        }
4637      })
4638    }
4639  }
4640
4641  impl ToTokens for WindowsConfig {
4642    fn to_tokens(&self, tokens: &mut TokenStream) {
4643      let webview_install_mode = &self.webview_install_mode;
4644      tokens.append_all(quote! { ::tauri::utils::config::WindowsConfig {
4645        webview_install_mode: #webview_install_mode,
4646        ..Default::default()
4647      }})
4648    }
4649  }
4650
4651  impl ToTokens for BundleResources {
4652    fn to_tokens(&self, tokens: &mut TokenStream) {
4653      let prefix = quote! { ::tauri::utils::config::BundleResources };
4654
4655      tokens.append_all(match self {
4656        Self::List(paths) => {
4657          let paths = vec_lit(paths, str_lit);
4658          quote! { #prefix::List(#paths) }
4659        }
4660        Self::Map(map) => {
4661          let map = map_lit(
4662            quote! { ::std::collections::HashMap },
4663            map,
4664            str_lit,
4665            str_lit,
4666          );
4667          quote! { #prefix::Map(#map) }
4668        }
4669      })
4670    }
4671  }
4672
4673  impl ToTokens for BundleConfig {
4674    fn to_tokens(&self, tokens: &mut TokenStream) {
4675      let publisher = quote!(None);
4676      let homepage = quote!(None);
4677      let icon = vec_lit(&self.icon, str_lit);
4678      let active = self.active;
4679      let targets = quote!(Default::default());
4680      let create_updater_artifacts = quote!(Default::default());
4681      let resources = opt_lit(self.resources.as_ref());
4682      let copyright = quote!(None);
4683      let category = quote!(None);
4684      let file_associations = quote!(None);
4685      let short_description = quote!(None);
4686      let long_description = quote!(None);
4687      let use_local_tools_dir = self.use_local_tools_dir;
4688      let external_bin = opt_vec_lit(self.external_bin.as_ref(), str_lit);
4689      let windows = &self.windows;
4690      let license = opt_str_lit(self.license.as_ref());
4691      let license_file = opt_lit(self.license_file.as_ref().map(path_buf_lit).as_ref());
4692      let linux = quote!(Default::default());
4693      let macos = quote!(Default::default());
4694      let ios = quote!(Default::default());
4695      let android = quote!(Default::default());
4696      let cef = quote!(Default::default());
4697
4698      literal_struct!(
4699        tokens,
4700        ::tauri::utils::config::BundleConfig,
4701        active,
4702        publisher,
4703        homepage,
4704        icon,
4705        targets,
4706        create_updater_artifacts,
4707        resources,
4708        copyright,
4709        category,
4710        license,
4711        license_file,
4712        file_associations,
4713        short_description,
4714        long_description,
4715        use_local_tools_dir,
4716        external_bin,
4717        windows,
4718        linux,
4719        macos,
4720        ios,
4721        android,
4722        cef
4723      );
4724    }
4725  }
4726
4727  impl ToTokens for FrontendDist {
4728    fn to_tokens(&self, tokens: &mut TokenStream) {
4729      let prefix = quote! { ::tauri::utils::config::FrontendDist };
4730
4731      tokens.append_all(match self {
4732        Self::Url(url) => {
4733          let url = url_lit(url);
4734          quote! { #prefix::Url(#url) }
4735        }
4736        Self::Directory(path) => {
4737          let path = path_buf_lit(path);
4738          quote! { #prefix::Directory(#path) }
4739        }
4740        Self::Files(files) => {
4741          let files = vec_lit(files, path_buf_lit);
4742          quote! { #prefix::Files(#files) }
4743        }
4744      })
4745    }
4746  }
4747
4748  impl ToTokens for RunnerConfig {
4749    fn to_tokens(&self, tokens: &mut TokenStream) {
4750      let prefix = quote! { ::tauri::utils::config::RunnerConfig };
4751
4752      tokens.append_all(match self {
4753        Self::String(cmd) => {
4754          let cmd = cmd.as_str();
4755          quote!(#prefix::String(#cmd.into()))
4756        }
4757        Self::Object { cmd, cwd, args } => {
4758          let cmd = cmd.as_str();
4759          let cwd = opt_str_lit(cwd.as_ref());
4760          let args = opt_lit(args.as_ref().map(|v| vec_lit(v, str_lit)).as_ref());
4761          quote!(#prefix::Object {
4762            cmd: #cmd.into(),
4763            cwd: #cwd,
4764            args: #args,
4765          })
4766        }
4767      })
4768    }
4769  }
4770
4771  impl ToTokens for BuildConfig {
4772    fn to_tokens(&self, tokens: &mut TokenStream) {
4773      let dev_url = opt_lit(self.dev_url.as_ref().map(url_lit).as_ref());
4774      let frontend_dist = opt_lit(self.frontend_dist.as_ref());
4775      let runner = opt_lit(self.runner.as_ref());
4776      let before_dev_command = quote!(None);
4777      let before_build_command = quote!(None);
4778      let before_bundle_command = quote!(None);
4779      let features = quote!(None);
4780      let remove_unused_commands = quote!(false);
4781      let additional_watch_folders = quote!(Vec::new());
4782      let windows = &self.windows;
4783
4784      literal_struct!(
4785        tokens,
4786        ::tauri::utils::config::BuildConfig,
4787        runner,
4788        dev_url,
4789        frontend_dist,
4790        before_dev_command,
4791        before_build_command,
4792        before_bundle_command,
4793        features,
4794        remove_unused_commands,
4795        additional_watch_folders,
4796        windows
4797      );
4798    }
4799  }
4800
4801  impl ToTokens for WindowsBuildConfig {
4802    fn to_tokens(&self, tokens: &mut TokenStream) {
4803      let static_vc_runtime = self.static_vc_runtime;
4804
4805      literal_struct!(
4806        tokens,
4807        ::tauri::utils::config::WindowsBuildConfig,
4808        static_vc_runtime
4809      );
4810    }
4811  }
4812
4813  impl ToTokens for CspDirectiveSources {
4814    fn to_tokens(&self, tokens: &mut TokenStream) {
4815      let prefix = quote! { ::tauri::utils::config::CspDirectiveSources };
4816
4817      tokens.append_all(match self {
4818        Self::Inline(sources) => {
4819          let sources = sources.as_str();
4820          quote!(#prefix::Inline(#sources.into()))
4821        }
4822        Self::List(list) => {
4823          let list = vec_lit(list, str_lit);
4824          quote!(#prefix::List(#list))
4825        }
4826      })
4827    }
4828  }
4829
4830  impl ToTokens for Csp {
4831    fn to_tokens(&self, tokens: &mut TokenStream) {
4832      let prefix = quote! { ::tauri::utils::config::Csp };
4833
4834      tokens.append_all(match self {
4835        Self::Policy(policy) => {
4836          let policy = policy.as_str();
4837          quote!(#prefix::Policy(#policy.into()))
4838        }
4839        Self::DirectiveMap(list) => {
4840          // Pass a sorted vec so the HashMap constructor is deterministic
4841          // see: https://github.com/tauri-apps/tauri/issues/14978
4842          // TODO: Remove this in v3, use a BTreeMap instead of a HashMap
4843          let mut sorted: Vec<_> = list.iter().collect();
4844          sorted.sort_by_key(|(k, _)| *k);
4845          let map = map_lit(
4846            quote! { ::std::collections::HashMap },
4847            sorted,
4848            str_lit,
4849            identity,
4850          );
4851          quote!(#prefix::DirectiveMap(#map))
4852        }
4853      })
4854    }
4855  }
4856
4857  impl ToTokens for DisabledCspModificationKind {
4858    fn to_tokens(&self, tokens: &mut TokenStream) {
4859      let prefix = quote! { ::tauri::utils::config::DisabledCspModificationKind };
4860
4861      tokens.append_all(match self {
4862        Self::Flag(flag) => {
4863          quote! { #prefix::Flag(#flag) }
4864        }
4865        Self::List(directives) => {
4866          let directives = vec_lit(directives, str_lit);
4867          quote! { #prefix::List(#directives) }
4868        }
4869      });
4870    }
4871  }
4872
4873  impl ToTokens for CapabilityEntry {
4874    fn to_tokens(&self, tokens: &mut TokenStream) {
4875      let prefix = quote! { ::tauri::utils::config::CapabilityEntry };
4876
4877      tokens.append_all(match self {
4878        Self::Inlined(capability) => {
4879          quote! { #prefix::Inlined(#capability) }
4880        }
4881        Self::Reference(id) => {
4882          let id = str_lit(id);
4883          quote! { #prefix::Reference(#id) }
4884        }
4885      });
4886    }
4887  }
4888
4889  impl ToTokens for HeaderSource {
4890    fn to_tokens(&self, tokens: &mut TokenStream) {
4891      let prefix = quote! { ::tauri::utils::config::HeaderSource };
4892
4893      tokens.append_all(match self {
4894        Self::Inline(s) => {
4895          let line = s.as_str();
4896          quote!(#prefix::Inline(#line.into()))
4897        }
4898        Self::List(l) => {
4899          let list = vec_lit(l, str_lit);
4900          quote!(#prefix::List(#list))
4901        }
4902        Self::Map(m) => {
4903          // Pass a sorted vec so the HashMap constructor is deterministic
4904          // see: https://github.com/tauri-apps/tauri/issues/14978
4905          // TODO: Remove this in v3, use a BTreeMap instead of a HashMap
4906          let mut sorted: Vec<_> = m.iter().collect();
4907          sorted.sort_by_key(|(k, _)| *k);
4908          let map = map_lit(
4909            quote! { ::std::collections::HashMap },
4910            sorted,
4911            str_lit,
4912            str_lit,
4913          );
4914          quote!(#prefix::Map(#map))
4915        }
4916      })
4917    }
4918  }
4919
4920  impl ToTokens for HeaderConfig {
4921    fn to_tokens(&self, tokens: &mut TokenStream) {
4922      let access_control_allow_credentials =
4923        opt_lit(self.access_control_allow_credentials.as_ref());
4924      let access_control_allow_headers = opt_lit(self.access_control_allow_headers.as_ref());
4925      let access_control_allow_methods = opt_lit(self.access_control_allow_methods.as_ref());
4926      let access_control_expose_headers = opt_lit(self.access_control_expose_headers.as_ref());
4927      let access_control_max_age = opt_lit(self.access_control_max_age.as_ref());
4928      let cross_origin_embedder_policy = opt_lit(self.cross_origin_embedder_policy.as_ref());
4929      let cross_origin_opener_policy = opt_lit(self.cross_origin_opener_policy.as_ref());
4930      let cross_origin_resource_policy = opt_lit(self.cross_origin_resource_policy.as_ref());
4931      let permissions_policy = opt_lit(self.permissions_policy.as_ref());
4932      let service_worker_allowed = opt_lit(self.service_worker_allowed.as_ref());
4933      let timing_allow_origin = opt_lit(self.timing_allow_origin.as_ref());
4934      let x_content_type_options = opt_lit(self.x_content_type_options.as_ref());
4935      let tauri_custom_header = opt_lit(self.tauri_custom_header.as_ref());
4936
4937      literal_struct!(
4938        tokens,
4939        ::tauri::utils::config::HeaderConfig,
4940        access_control_allow_credentials,
4941        access_control_allow_headers,
4942        access_control_allow_methods,
4943        access_control_expose_headers,
4944        access_control_max_age,
4945        cross_origin_embedder_policy,
4946        cross_origin_opener_policy,
4947        cross_origin_resource_policy,
4948        permissions_policy,
4949        service_worker_allowed,
4950        timing_allow_origin,
4951        x_content_type_options,
4952        tauri_custom_header
4953      );
4954    }
4955  }
4956
4957  impl ToTokens for SecurityConfig {
4958    fn to_tokens(&self, tokens: &mut TokenStream) {
4959      let csp = opt_lit(self.csp.as_ref());
4960      let dev_csp = opt_lit(self.dev_csp.as_ref());
4961      let freeze_prototype = self.freeze_prototype;
4962      let dangerous_disable_asset_csp_modification = &self.dangerous_disable_asset_csp_modification;
4963      let asset_protocol = &self.asset_protocol;
4964      let pattern = &self.pattern;
4965      let capabilities = vec_lit(&self.capabilities, identity);
4966      let headers = opt_lit(self.headers.as_ref());
4967
4968      literal_struct!(
4969        tokens,
4970        ::tauri::utils::config::SecurityConfig,
4971        csp,
4972        dev_csp,
4973        freeze_prototype,
4974        dangerous_disable_asset_csp_modification,
4975        asset_protocol,
4976        pattern,
4977        capabilities,
4978        headers
4979      );
4980    }
4981  }
4982
4983  impl ToTokens for TrayIconConfig {
4984    fn to_tokens(&self, tokens: &mut TokenStream) {
4985      // For [`Self::menu_on_left_click`]
4986      tokens.append_all(quote!(#[allow(deprecated)]));
4987
4988      let id = opt_str_lit(self.id.as_ref());
4989      let icon_as_template = self.icon_as_template;
4990      #[allow(deprecated)]
4991      let menu_on_left_click = self.menu_on_left_click;
4992      let show_menu_on_left_click = self.show_menu_on_left_click;
4993      let icon_path = path_buf_lit(&self.icon_path);
4994      let title = opt_str_lit(self.title.as_ref());
4995      let tooltip = opt_str_lit(self.tooltip.as_ref());
4996      literal_struct!(
4997        tokens,
4998        ::tauri::utils::config::TrayIconConfig,
4999        id,
5000        icon_path,
5001        icon_as_template,
5002        menu_on_left_click,
5003        show_menu_on_left_click,
5004        title,
5005        tooltip
5006      );
5007    }
5008  }
5009
5010  impl ToTokens for FsScope {
5011    fn to_tokens(&self, tokens: &mut TokenStream) {
5012      let prefix = quote! { ::tauri::utils::config::FsScope };
5013
5014      tokens.append_all(match self {
5015        Self::AllowedPaths(allow) => {
5016          let allowed_paths = vec_lit(allow, path_buf_lit);
5017          quote! { #prefix::AllowedPaths(#allowed_paths) }
5018        }
5019        Self::Scope { allow, deny , require_literal_leading_dot} => {
5020          let allow = vec_lit(allow, path_buf_lit);
5021          let deny = vec_lit(deny, path_buf_lit);
5022          let  require_literal_leading_dot = opt_lit(require_literal_leading_dot.as_ref());
5023          quote! { #prefix::Scope { allow: #allow, deny: #deny, require_literal_leading_dot: #require_literal_leading_dot } }
5024        }
5025      });
5026    }
5027  }
5028
5029  impl ToTokens for AssetProtocolConfig {
5030    fn to_tokens(&self, tokens: &mut TokenStream) {
5031      let scope = &self.scope;
5032      tokens.append_all(quote! { ::tauri::utils::config::AssetProtocolConfig { scope: #scope, ..Default::default() } })
5033    }
5034  }
5035
5036  impl ToTokens for AppDirectoryOverrides {
5037    fn to_tokens(&self, tokens: &mut TokenStream) {
5038      let config = opt_lit_owned(self.config.as_ref().map(path_buf_lit));
5039      let data = opt_lit_owned(self.data.as_ref().map(path_buf_lit));
5040      let local_data = opt_lit_owned(self.local_data.as_ref().map(path_buf_lit));
5041      let cache = opt_lit_owned(self.cache.as_ref().map(path_buf_lit));
5042      let log = opt_lit_owned(self.log.as_ref().map(path_buf_lit));
5043
5044      literal_struct!(
5045        tokens,
5046        ::tauri::utils::config::AppDirectoryOverrides,
5047        config,
5048        data,
5049        local_data,
5050        cache,
5051        log
5052      );
5053    }
5054  }
5055
5056  impl ToTokens for AppDirectoriesOverride {
5057    fn to_tokens(&self, tokens: &mut TokenStream) {
5058      let prefix = quote! { ::tauri::utils::config::AppDirectoriesOverride };
5059
5060      tokens.append_all(match self {
5061        Self::Root(root) => {
5062          let root = path_buf_lit(root);
5063          quote! { #prefix::Root(#root) }
5064        }
5065        Self::Directories(directories) => quote! { #prefix::Directories(#directories) },
5066      })
5067    }
5068  }
5069
5070  impl ToTokens for AppConfig {
5071    fn to_tokens(&self, tokens: &mut TokenStream) {
5072      let windows = vec_lit(&self.windows, identity);
5073      let security = &self.security;
5074      let tray_icon = opt_lit(self.tray_icon.as_ref());
5075      let with_global_tauri = self.with_global_tauri;
5076      let enable_gtk_app_id = self.enable_gtk_app_id;
5077      let app_directories_override = opt_lit(self.app_directories_override.as_ref());
5078
5079      literal_struct!(
5080        tokens,
5081        ::tauri::utils::config::AppConfig,
5082        windows,
5083        security,
5084        tray_icon,
5085        with_global_tauri,
5086        enable_gtk_app_id,
5087        app_directories_override
5088      );
5089    }
5090  }
5091
5092  impl ToTokens for PluginConfig {
5093    fn to_tokens(&self, tokens: &mut TokenStream) {
5094      // Pass a sorted vec so the HashMap constructor is deterministic
5095      // see: https://github.com/tauri-apps/tauri/issues/14978
5096      // TODO: Remove this in v3, use a BTreeMap instead of a HashMap
5097      let mut sorted: Vec<_> = self.0.iter().collect();
5098      sorted.sort_by_key(|(k, _)| *k);
5099      let config = map_lit(
5100        quote! { ::std::collections::HashMap },
5101        sorted,
5102        str_lit,
5103        json_value_lit,
5104      );
5105      tokens.append_all(quote! { ::tauri::utils::config::PluginConfig(#config) })
5106    }
5107  }
5108
5109  impl ToTokens for Config {
5110    fn to_tokens(&self, tokens: &mut TokenStream) {
5111      let schema = quote!(None);
5112      let product_name = opt_str_lit(self.product_name.as_ref());
5113      let main_binary_name = opt_str_lit(self.main_binary_name.as_ref());
5114      let version = opt_str_lit(self.version.as_ref());
5115      let identifier = str_lit(&self.identifier);
5116      let app = &self.app;
5117      let build = &self.build;
5118      let bundle = &self.bundle;
5119      let plugins = &self.plugins;
5120
5121      literal_struct!(
5122        tokens,
5123        ::tauri::utils::config::Config,
5124        schema,
5125        product_name,
5126        main_binary_name,
5127        version,
5128        identifier,
5129        app,
5130        build,
5131        bundle,
5132        plugins
5133      );
5134    }
5135  }
5136}
5137
5138#[cfg(test)]
5139mod test {
5140  use super::*;
5141
5142  // TODO: create a test that compares a config to a json config
5143
5144  #[test]
5145  // test all of the default functions
5146  fn test_defaults() {
5147    // get default app config
5148    let a_config = AppConfig::default();
5149    // get default build config
5150    let b_config = BuildConfig::default();
5151    // get default window
5152    let d_windows: Vec<WindowConfig> = vec![];
5153    // get default bundle
5154    let d_bundle = BundleConfig::default();
5155
5156    // create a tauri config.
5157    let app = AppConfig {
5158      windows: vec![],
5159      security: SecurityConfig {
5160        csp: None,
5161        dev_csp: None,
5162        freeze_prototype: false,
5163        dangerous_disable_asset_csp_modification: DisabledCspModificationKind::Flag(false),
5164        asset_protocol: AssetProtocolConfig::default(),
5165        pattern: Default::default(),
5166        capabilities: Vec::new(),
5167        headers: None,
5168      },
5169      tray_icon: None,
5170      with_global_tauri: false,
5171      enable_gtk_app_id: false,
5172      app_directories_override: None,
5173    };
5174
5175    // create a build config
5176    let build = BuildConfig {
5177      runner: None,
5178      dev_url: None,
5179      frontend_dist: None,
5180      before_dev_command: None,
5181      before_build_command: None,
5182      before_bundle_command: None,
5183      features: None,
5184      remove_unused_commands: false,
5185      additional_watch_folders: Vec::new(),
5186      windows: WindowsBuildConfig::default(),
5187    };
5188
5189    // create a bundle config
5190    let bundle = BundleConfig {
5191      active: false,
5192      targets: Default::default(),
5193      create_updater_artifacts: Default::default(),
5194      publisher: None,
5195      homepage: None,
5196      icon: Vec::new(),
5197      resources: None,
5198      copyright: None,
5199      category: None,
5200      file_associations: None,
5201      short_description: None,
5202      long_description: None,
5203      use_local_tools_dir: false,
5204      license: None,
5205      license_file: None,
5206      linux: Default::default(),
5207      macos: Default::default(),
5208      external_bin: None,
5209      windows: Default::default(),
5210      ios: Default::default(),
5211      android: Default::default(),
5212      cef: Default::default(),
5213    };
5214
5215    // test the configs
5216    assert_eq!(a_config, app);
5217    assert_eq!(b_config, build);
5218    assert_eq!(d_bundle, bundle);
5219    assert_eq!(d_windows, app.windows);
5220  }
5221
5222  #[test]
5223  fn app_directories_override_root() {
5224    let config: AppDirectoriesOverride = serde_json::from_str(r#""./""#).unwrap();
5225    assert_eq!(config, AppDirectoriesOverride::Root("./".into()));
5226
5227    let config: AppDirectoriesOverride = serde_json::from_str(r#""$DATA/my-app""#).unwrap();
5228    assert_eq!(config, AppDirectoriesOverride::Root("$DATA/my-app".into()));
5229  }
5230
5231  #[test]
5232  fn app_directories_override_directories() {
5233    let config: AppDirectoriesOverride = serde_json::from_str(
5234      r#"{ "log": "$DATA/logs", "cache": "$CACHE/my-app", "local-data": "data" }"#,
5235    )
5236    .unwrap();
5237    assert_eq!(
5238      config,
5239      AppDirectoriesOverride::Directories(AppDirectoryOverrides {
5240        config: None,
5241        data: None,
5242        local_data: Some("data".into()),
5243        cache: Some("$CACHE/my-app".into()),
5244        log: Some("$DATA/logs".into()),
5245      })
5246    );
5247
5248    let config: AppDirectoriesOverride =
5249      serde_json::from_str(r#"{ "config": "conf", "data": "data", "localData": "local" }"#)
5250        .unwrap();
5251    assert_eq!(
5252      config,
5253      AppDirectoriesOverride::Directories(AppDirectoryOverrides {
5254        config: Some("conf".into()),
5255        data: Some("data".into()),
5256        local_data: Some("local".into()),
5257        cache: None,
5258        log: None,
5259      })
5260    );
5261
5262    let config: AppDirectoriesOverride = serde_json::from_str("{}").unwrap();
5263    assert_eq!(
5264      config,
5265      AppDirectoriesOverride::Directories(AppDirectoryOverrides::default())
5266    );
5267  }
5268
5269  #[test]
5270  fn app_directories_override_rejects_unknown_directories() {
5271    let err = serde_json::from_str::<AppDirectoriesOverride>(r#"{ "logs": "x" }"#).unwrap_err();
5272    assert!(err.to_string().contains("unknown field `logs`"), "{err}");
5273  }
5274
5275  #[test]
5276  fn app_directories_override_accepts_supported_variables() {
5277    for variable in APP_DIRECTORIES_OVERRIDE_VARIABLES {
5278      for path in [
5279        variable.to_string(),
5280        format!("{variable}/my-app"),
5281        format!("{variable}/../my-app"),
5282      ] {
5283        let json = serde_json::to_string(&path).unwrap();
5284        let config: AppDirectoriesOverride = serde_json::from_str(&json).unwrap();
5285        assert_eq!(config, AppDirectoriesOverride::Root(path.into()));
5286      }
5287    }
5288  }
5289
5290  #[test]
5291  fn app_directories_override_rejects_unsupported_variables() {
5292    for variable in [
5293      "$APPCONFIG",
5294      "$APPDATA",
5295      "$APPLOCALDATA",
5296      "$APPCACHE",
5297      "$APPLOG",
5298      "$EXE",
5299      "$FONT",
5300      "$RESOURCE",
5301      "$RUNTIME",
5302      "$TEMPLATE",
5303      "$UNKNOWN",
5304    ] {
5305      let err = serde_json::from_str::<AppDirectoriesOverride>(&format!(r#""{variable}/my-app""#))
5306        .unwrap_err();
5307      assert!(
5308        err
5309          .to_string()
5310          .contains(&format!("unsupported base directory variable `{variable}`")),
5311        "{variable}: {err}"
5312      );
5313
5314      let err =
5315        serde_json::from_str::<AppDirectoriesOverride>(&format!(r#"{{ "log": "{variable}" }}"#))
5316          .unwrap_err();
5317      assert!(
5318        err
5319          .to_string()
5320          .contains("unsupported base directory variable"),
5321        "{variable}: {err}"
5322      );
5323    }
5324  }
5325
5326  #[cfg(windows)]
5327  #[test]
5328  fn app_directories_override_rejects_root_relative_paths() {
5329    for path in [r"\my-app", "C:my-app"] {
5330      let json = serde_json::to_string(path).unwrap();
5331      let err = serde_json::from_str::<AppDirectoriesOverride>(&json).unwrap_err();
5332      assert!(
5333        err.to_string().contains("must be an absolute path"),
5334        "{path}: {err}"
5335      );
5336    }
5337  }
5338
5339  #[cfg(feature = "build")]
5340  #[test]
5341  fn app_directories_override_to_tokens() {
5342    use quote::ToTokens;
5343
5344    let tokens = AppDirectoriesOverride::Root("./".into())
5345      .to_token_stream()
5346      .to_string()
5347      .replace(' ', "");
5348    assert_eq!(
5349      tokens,
5350      r#"::tauri::utils::config::AppDirectoriesOverride::Root(::std::path::PathBuf::from("./"))"#
5351    );
5352
5353    let tokens = AppDirectoriesOverride::Directories(AppDirectoryOverrides {
5354      log: Some("$DATA/logs".into()),
5355      ..Default::default()
5356    })
5357    .to_token_stream()
5358    .to_string()
5359    .replace(' ', "");
5360    assert_eq!(
5361      tokens,
5362      r#"::tauri::utils::config::AppDirectoriesOverride::Directories(::tauri::utils::config::AppDirectoryOverrides{config:::core::option::Option::None,data:::core::option::Option::None,local_data:::core::option::Option::None,cache:::core::option::Option::None,log:::core::option::Option::Some(::std::path::PathBuf::from("$DATA/logs"))})"#
5363    );
5364  }
5365
5366  #[test]
5367  fn parse_hex_color() {
5368    use super::Color;
5369
5370    assert_eq!(Color(255, 255, 255, 255), "fff".parse().unwrap());
5371    assert_eq!(Color(255, 255, 255, 255), "#fff".parse().unwrap());
5372    assert_eq!(Color(0, 0, 0, 255), "#000000".parse().unwrap());
5373    assert_eq!(Color(0, 0, 0, 255), "#000000ff".parse().unwrap());
5374    assert_eq!(Color(0, 255, 0, 255), "#00ff00ff".parse().unwrap());
5375  }
5376
5377  #[test]
5378  fn test_runner_config_string_format() {
5379    use super::RunnerConfig;
5380
5381    // Test string format deserialization
5382    let json = r#""cargo""#;
5383    let runner: RunnerConfig = serde_json::from_str(json).unwrap();
5384
5385    assert_eq!(runner.cmd(), "cargo");
5386    assert_eq!(runner.cwd(), None);
5387    assert_eq!(runner.args(), None);
5388
5389    // Test string format serialization
5390    let serialized = serde_json::to_string(&runner).unwrap();
5391    assert_eq!(serialized, r#""cargo""#);
5392  }
5393
5394  #[test]
5395  fn test_runner_config_object_format_full() {
5396    use super::RunnerConfig;
5397
5398    // Test object format with all fields
5399    let json = r#"{"cmd": "my_runner", "cwd": "/tmp/build", "args": ["--quiet", "--verbose"]}"#;
5400    let runner: RunnerConfig = serde_json::from_str(json).unwrap();
5401
5402    assert_eq!(runner.cmd(), "my_runner");
5403    assert_eq!(runner.cwd(), Some("/tmp/build"));
5404    assert_eq!(
5405      runner.args(),
5406      Some(&["--quiet".to_string(), "--verbose".to_string()][..])
5407    );
5408
5409    // Test object format serialization
5410    let serialized = serde_json::to_string(&runner).unwrap();
5411    let deserialized: RunnerConfig = serde_json::from_str(&serialized).unwrap();
5412    assert_eq!(runner, deserialized);
5413  }
5414
5415  #[test]
5416  fn test_runner_config_object_format_minimal() {
5417    use super::RunnerConfig;
5418
5419    // Test object format with only cmd field
5420    let json = r#"{"cmd": "cross"}"#;
5421    let runner: RunnerConfig = serde_json::from_str(json).unwrap();
5422
5423    assert_eq!(runner.cmd(), "cross");
5424    assert_eq!(runner.cwd(), None);
5425    assert_eq!(runner.args(), None);
5426  }
5427
5428  #[test]
5429  fn test_runner_config_default() {
5430    use super::RunnerConfig;
5431
5432    let default_runner = RunnerConfig::default();
5433    assert_eq!(default_runner.cmd(), "cargo");
5434    assert_eq!(default_runner.cwd(), None);
5435    assert_eq!(default_runner.args(), None);
5436  }
5437
5438  #[test]
5439  fn test_runner_config_from_str() {
5440    use super::RunnerConfig;
5441
5442    // Test From<&str> trait
5443    let runner: RunnerConfig = "my_runner".into();
5444    assert_eq!(runner.cmd(), "my_runner");
5445    assert_eq!(runner.cwd(), None);
5446    assert_eq!(runner.args(), None);
5447  }
5448
5449  #[test]
5450  fn test_runner_config_from_string() {
5451    use super::RunnerConfig;
5452
5453    // Test From<String> trait
5454    let runner: RunnerConfig = "another_runner".to_string().into();
5455    assert_eq!(runner.cmd(), "another_runner");
5456    assert_eq!(runner.cwd(), None);
5457    assert_eq!(runner.args(), None);
5458  }
5459
5460  #[test]
5461  fn test_runner_config_from_str_parse() {
5462    use super::RunnerConfig;
5463    use std::str::FromStr;
5464
5465    // Test FromStr trait
5466    let runner = RunnerConfig::from_str("parsed_runner").unwrap();
5467    assert_eq!(runner.cmd(), "parsed_runner");
5468    assert_eq!(runner.cwd(), None);
5469    assert_eq!(runner.args(), None);
5470  }
5471
5472  #[test]
5473  fn test_runner_config_in_build_config() {
5474    use super::BuildConfig;
5475
5476    // Test string format in BuildConfig
5477    let json = r#"{"runner": "cargo"}"#;
5478    let build_config: BuildConfig = serde_json::from_str(json).unwrap();
5479
5480    let runner = build_config.runner.unwrap();
5481    assert_eq!(runner.cmd(), "cargo");
5482    assert_eq!(runner.cwd(), None);
5483    assert_eq!(runner.args(), None);
5484  }
5485
5486  #[test]
5487  fn test_runner_config_in_build_config_object() {
5488    use super::BuildConfig;
5489
5490    // Test object format in BuildConfig
5491    let json = r#"{"runner": {"cmd": "cross", "cwd": "/workspace", "args": ["--target", "x86_64-unknown-linux-gnu"]}}"#;
5492    let build_config: BuildConfig = serde_json::from_str(json).unwrap();
5493
5494    let runner = build_config.runner.unwrap();
5495    assert_eq!(runner.cmd(), "cross");
5496    assert_eq!(runner.cwd(), Some("/workspace"));
5497    assert_eq!(
5498      runner.args(),
5499      Some(
5500        &[
5501          "--target".to_string(),
5502          "x86_64-unknown-linux-gnu".to_string()
5503        ][..]
5504      )
5505    );
5506  }
5507
5508  #[test]
5509  fn test_runner_config_in_full_config() {
5510    use super::Config;
5511
5512    // Test runner config in full Tauri config
5513    let json = r#"{
5514      "productName": "Test App",
5515      "version": "1.0.0",
5516      "identifier": "com.test.app",
5517      "build": {
5518        "runner": {
5519          "cmd": "my_custom_cargo",
5520          "cwd": "/tmp/build",
5521          "args": ["--quiet", "--verbose"]
5522        }
5523      }
5524    }"#;
5525
5526    let config: Config = serde_json::from_str(json).unwrap();
5527    let runner = config.build.runner.unwrap();
5528
5529    assert_eq!(runner.cmd(), "my_custom_cargo");
5530    assert_eq!(runner.cwd(), Some("/tmp/build"));
5531    assert_eq!(
5532      runner.args(),
5533      Some(&["--quiet".to_string(), "--verbose".to_string()][..])
5534    );
5535  }
5536
5537  #[test]
5538  fn test_runner_config_equality() {
5539    use super::RunnerConfig;
5540
5541    let runner1 = RunnerConfig::String("cargo".to_string());
5542    let runner2 = RunnerConfig::String("cargo".to_string());
5543    let runner3 = RunnerConfig::String("cross".to_string());
5544
5545    assert_eq!(runner1, runner2);
5546    assert_ne!(runner1, runner3);
5547
5548    let runner4 = RunnerConfig::Object {
5549      cmd: "cargo".to_string(),
5550      cwd: Some("/tmp".to_string()),
5551      args: Some(vec!["--quiet".to_string()]),
5552    };
5553    let runner5 = RunnerConfig::Object {
5554      cmd: "cargo".to_string(),
5555      cwd: Some("/tmp".to_string()),
5556      args: Some(vec!["--quiet".to_string()]),
5557    };
5558
5559    assert_eq!(runner4, runner5);
5560    assert_ne!(runner1, runner4);
5561  }
5562
5563  #[test]
5564  fn test_runner_config_untagged_serialization() {
5565    use super::RunnerConfig;
5566
5567    // Test that serde untagged works correctly - string should serialize as string, not object
5568    let string_runner = RunnerConfig::String("cargo".to_string());
5569    let string_json = serde_json::to_string(&string_runner).unwrap();
5570    assert_eq!(string_json, r#""cargo""#);
5571
5572    // Test that object serializes as object
5573    let object_runner = RunnerConfig::Object {
5574      cmd: "cross".to_string(),
5575      cwd: None,
5576      args: None,
5577    };
5578    let object_json = serde_json::to_string(&object_runner).unwrap();
5579    assert!(object_json.contains("\"cmd\":\"cross\""));
5580    // With skip_serializing_none, null values should not be included
5581    assert!(object_json.contains("\"cwd\":null") || !object_json.contains("cwd"));
5582    assert!(object_json.contains("\"args\":null") || !object_json.contains("args"));
5583  }
5584
5585  #[test]
5586  fn header_source_map_display_is_deterministic() {
5587    let map = HashMap::from([
5588      ("key3".to_string(), "'value3'".to_string()),
5589      ("key1".to_string(), "'value1' 'value2'".to_string()),
5590      ("key2".to_string(), "'value4'".to_string()),
5591    ]);
5592
5593    // the value must be sorted by key and stable across runs and across `HashMap` orderings
5594    assert_eq!(
5595      HeaderSource::Map(map.clone()).to_string(),
5596      "key1 'value1' 'value2'; key2 'value4'; key3 'value3'"
5597    );
5598
5599    let expected = HeaderSource::Map(map).to_string();
5600    for _ in 0..10 {
5601      let map = HashMap::from([
5602        ("key2".to_string(), "'value4'".to_string()),
5603        ("key3".to_string(), "'value3'".to_string()),
5604        ("key1".to_string(), "'value1' 'value2'".to_string()),
5605      ]);
5606      assert_eq!(HeaderSource::Map(map).to_string(), expected);
5607    }
5608
5609    // `Serialize` must keep matching `Display`'s ordering
5610    let map = HashMap::from([
5611      ("b".to_string(), "2".to_string()),
5612      ("a".to_string(), "1".to_string()),
5613    ]);
5614    assert_eq!(
5615      serde_json::to_string(&HeaderSource::Map(map)).unwrap(),
5616      r#"{"a":"1","b":"2"}"#
5617    );
5618  }
5619
5620  #[test]
5621  fn header_source_display() {
5622    assert_eq!(
5623      HeaderSource::Inline("same-origin".into()).to_string(),
5624      "same-origin"
5625    );
5626    assert_eq!(
5627      HeaderSource::List(vec!["https://a.example".into(), "https://b.example".into()]).to_string(),
5628      "https://a.example, https://b.example"
5629    );
5630  }
5631
5632  #[test]
5633  fn window_config_default_same_as_deserialize() {
5634    let config_from_deserialization: WindowConfig = serde_json::from_str("{}").unwrap();
5635    let config_from_default: WindowConfig = WindowConfig::default();
5636
5637    assert_eq!(config_from_deserialization, config_from_default);
5638  }
5639}