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
26use http::response::Builder;
27#[cfg(feature = "schema")]
28use schemars::JsonSchema;
29use semver::Version;
30use serde::{
31  de::{Deserializer, Error as DeError, Visitor},
32  Deserialize, Serialize, Serializer,
33};
34use serde_json::Value as JsonValue;
35use serde_untagged::UntaggedEnumVisitor;
36use serde_with::skip_serializing_none;
37use url::Url;
38
39use std::{
40  collections::HashMap,
41  fmt::{self, Display},
42  fs::read_to_string,
43  path::PathBuf,
44  str::FromStr,
45};
46
47/// Items to help with parsing content into a [`Config`].
48pub mod parse;
49
50use crate::{acl::capability::Capability, TitleBarStyle, WindowEffect, WindowEffectState};
51
52pub use self::parse::parse;
53
54fn default_true() -> bool {
55  true
56}
57
58/// An URL to open on a Tauri webview window.
59#[derive(PartialEq, Eq, Debug, Clone, Serialize)]
60#[cfg_attr(feature = "schema", derive(JsonSchema))]
61#[serde(untagged)]
62#[non_exhaustive]
63pub enum WebviewUrl {
64  /// An external URL. Must use either the `http` or `https` schemes.
65  External(Url),
66  /// The path portion of an app URL.
67  /// For instance, to load `tauri://localhost/users/john`,
68  /// you can simply provide `users/john` in this configuration.
69  App(PathBuf),
70  /// A custom protocol url, for example, `doom://index.html`
71  CustomProtocol(Url),
72}
73
74impl<'de> Deserialize<'de> for WebviewUrl {
75  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
76  where
77    D: Deserializer<'de>,
78  {
79    #[derive(Deserialize)]
80    #[serde(untagged)]
81    enum WebviewUrlDeserializer {
82      Url(Url),
83      Path(PathBuf),
84    }
85
86    match WebviewUrlDeserializer::deserialize(deserializer)? {
87      WebviewUrlDeserializer::Url(u) => {
88        if u.scheme() == "https" || u.scheme() == "http" {
89          Ok(Self::External(u))
90        } else {
91          Ok(Self::CustomProtocol(u))
92        }
93      }
94      WebviewUrlDeserializer::Path(p) => Ok(Self::App(p)),
95    }
96  }
97}
98
99impl fmt::Display for WebviewUrl {
100  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101    match self {
102      Self::External(url) | Self::CustomProtocol(url) => write!(f, "{url}"),
103      Self::App(path) => write!(f, "{}", path.display()),
104    }
105  }
106}
107
108impl Default for WebviewUrl {
109  fn default() -> Self {
110    Self::App("index.html".into())
111  }
112}
113
114/// A bundle referenced by tauri-bundler.
115#[derive(Debug, PartialEq, Eq, Clone)]
116#[cfg_attr(feature = "schema", derive(JsonSchema))]
117#[cfg_attr(feature = "schema", schemars(rename_all = "lowercase"))]
118pub enum BundleType {
119  /// The debian bundle (.deb).
120  Deb,
121  /// The RPM bundle (.rpm).
122  Rpm,
123  /// The AppImage bundle (.appimage).
124  AppImage,
125  /// The Microsoft Installer bundle (.msi).
126  Msi,
127  /// The NSIS bundle (.exe).
128  Nsis,
129  /// The macOS application bundle (.app).
130  App,
131  /// The Apple Disk Image bundle (.dmg).
132  Dmg,
133}
134
135impl BundleType {
136  /// All bundle types.
137  fn all() -> &'static [Self] {
138    &[
139      BundleType::Deb,
140      BundleType::Rpm,
141      BundleType::AppImage,
142      BundleType::Msi,
143      BundleType::Nsis,
144      BundleType::App,
145      BundleType::Dmg,
146    ]
147  }
148}
149
150impl Display for BundleType {
151  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152    write!(
153      f,
154      "{}",
155      match self {
156        Self::Deb => "deb",
157        Self::Rpm => "rpm",
158        Self::AppImage => "appimage",
159        Self::Msi => "msi",
160        Self::Nsis => "nsis",
161        Self::App => "app",
162        Self::Dmg => "dmg",
163      }
164    )
165  }
166}
167
168impl Serialize for BundleType {
169  fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
170  where
171    S: Serializer,
172  {
173    serializer.serialize_str(self.to_string().as_ref())
174  }
175}
176
177impl<'de> Deserialize<'de> for BundleType {
178  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
179  where
180    D: Deserializer<'de>,
181  {
182    let s = String::deserialize(deserializer)?;
183    match s.to_lowercase().as_str() {
184      "deb" => Ok(Self::Deb),
185      "rpm" => Ok(Self::Rpm),
186      "appimage" => Ok(Self::AppImage),
187      "msi" => Ok(Self::Msi),
188      "nsis" => Ok(Self::Nsis),
189      "app" => Ok(Self::App),
190      "dmg" => Ok(Self::Dmg),
191      _ => Err(DeError::custom(format!("unknown bundle target '{s}'"))),
192    }
193  }
194}
195
196/// Targets to bundle. Each value is case insensitive.
197#[derive(Debug, PartialEq, Eq, Clone)]
198pub enum BundleTarget {
199  /// Bundle all targets.
200  All,
201  /// A list of bundle targets.
202  List(Vec<BundleType>),
203  /// A single bundle target.
204  One(BundleType),
205}
206
207#[cfg(feature = "schema")]
208impl schemars::JsonSchema for BundleTarget {
209  fn schema_name() -> std::string::String {
210    "BundleTarget".to_owned()
211  }
212
213  fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
214    let any_of = vec![
215      schemars::schema::SchemaObject {
216        const_value: Some("all".into()),
217        metadata: Some(Box::new(schemars::schema::Metadata {
218          description: Some("Bundle all targets.".to_owned()),
219          ..Default::default()
220        })),
221        ..Default::default()
222      }
223      .into(),
224      schemars::_private::metadata::add_description(
225        gen.subschema_for::<Vec<BundleType>>(),
226        "A list of bundle targets.",
227      ),
228      schemars::_private::metadata::add_description(
229        gen.subschema_for::<BundleType>(),
230        "A single bundle target.",
231      ),
232    ];
233
234    schemars::schema::SchemaObject {
235      subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
236        any_of: Some(any_of),
237        ..Default::default()
238      })),
239      metadata: Some(Box::new(schemars::schema::Metadata {
240        description: Some("Targets to bundle. Each value is case insensitive.".to_owned()),
241        ..Default::default()
242      })),
243      ..Default::default()
244    }
245    .into()
246  }
247}
248
249impl Default for BundleTarget {
250  fn default() -> Self {
251    Self::All
252  }
253}
254
255impl Serialize for BundleTarget {
256  fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
257  where
258    S: Serializer,
259  {
260    match self {
261      Self::All => serializer.serialize_str("all"),
262      Self::List(l) => l.serialize(serializer),
263      Self::One(t) => serializer.serialize_str(t.to_string().as_ref()),
264    }
265  }
266}
267
268impl<'de> Deserialize<'de> for BundleTarget {
269  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
270  where
271    D: Deserializer<'de>,
272  {
273    #[derive(Deserialize, Serialize)]
274    #[serde(untagged)]
275    pub enum BundleTargetInner {
276      List(Vec<BundleType>),
277      One(BundleType),
278      All(String),
279    }
280
281    match BundleTargetInner::deserialize(deserializer)? {
282      BundleTargetInner::All(s) if s.to_lowercase() == "all" => Ok(Self::All),
283      BundleTargetInner::All(t) => Err(DeError::custom(format!(
284        "invalid bundle type {t}, expected one of `all`, {}",
285        BundleType::all()
286          .iter()
287          .map(|b| format!("`{b}`"))
288          .collect::<Vec<_>>()
289          .join(", ")
290      ))),
291      BundleTargetInner::List(l) => Ok(Self::List(l)),
292      BundleTargetInner::One(t) => Ok(Self::One(t)),
293    }
294  }
295}
296
297impl BundleTarget {
298  /// Gets the bundle targets as a [`Vec`]. The vector is empty when set to [`BundleTarget::All`].
299  #[allow(dead_code)]
300  pub fn to_vec(&self) -> Vec<BundleType> {
301    match self {
302      Self::All => BundleType::all().to_vec(),
303      Self::List(list) => list.clone(),
304      Self::One(i) => vec![i.clone()],
305    }
306  }
307}
308
309/// Configuration for AppImage bundles.
310///
311/// See more: <https://v2.tauri.app/reference/config/#appimageconfig>
312#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
313#[cfg_attr(feature = "schema", derive(JsonSchema))]
314#[serde(rename_all = "camelCase", deny_unknown_fields)]
315pub struct AppImageConfig {
316  /// Include additional gstreamer dependencies needed for audio and video playback.
317  /// This increases the bundle size by ~15-35MB depending on your build system.
318  #[serde(default, alias = "bundle-media-framework")]
319  pub bundle_media_framework: bool,
320  /// The files to include in the Appimage Binary.
321  #[serde(default)]
322  pub files: HashMap<PathBuf, PathBuf>,
323}
324
325/// Configuration for Debian (.deb) bundles.
326///
327/// See more: <https://v2.tauri.app/reference/config/#debconfig>
328#[skip_serializing_none]
329#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
330#[cfg_attr(feature = "schema", derive(JsonSchema))]
331#[serde(rename_all = "camelCase", deny_unknown_fields)]
332pub struct DebConfig {
333  /// The list of deb dependencies your application relies on.
334  pub depends: Option<Vec<String>>,
335  /// The list of deb dependencies your application recommends.
336  pub recommends: Option<Vec<String>>,
337  /// The list of dependencies the package provides.
338  pub provides: Option<Vec<String>>,
339  /// The list of package conflicts.
340  pub conflicts: Option<Vec<String>>,
341  /// The list of package replaces.
342  pub replaces: Option<Vec<String>>,
343  /// The files to include on the package.
344  #[serde(default)]
345  pub files: HashMap<PathBuf, PathBuf>,
346  /// Define the section in Debian Control file. See : https://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections
347  pub section: Option<String>,
348  /// Change the priority of the Debian Package. By default, it is set to `optional`.
349  /// Recognized Priorities as of now are :  `required`, `important`, `standard`, `optional`, `extra`
350  pub priority: Option<String>,
351  /// Path of the uncompressed Changelog file, to be stored at /usr/share/doc/package-name/changelog.gz. See
352  /// <https://www.debian.org/doc/debian-policy/ch-docs.html#changelog-files-and-release-notes>
353  pub changelog: Option<PathBuf>,
354  /// Path to a custom desktop file Handlebars template.
355  ///
356  /// Available variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.
357  #[serde(alias = "desktop-template")]
358  pub desktop_template: Option<PathBuf>,
359  /// Path to script that will be executed before the package is unpacked. See
360  /// <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>
361  #[serde(alias = "pre-install-script")]
362  pub pre_install_script: Option<PathBuf>,
363  /// Path to script that will be executed after the package is unpacked. See
364  /// <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>
365  #[serde(alias = "post-install-script")]
366  pub post_install_script: Option<PathBuf>,
367  /// Path to script that will be executed before the package is removed. See
368  /// <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>
369  #[serde(alias = "pre-remove-script")]
370  pub pre_remove_script: Option<PathBuf>,
371  /// Path to script that will be executed after the package is removed. See
372  /// <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>
373  #[serde(alias = "post-remove-script")]
374  pub post_remove_script: Option<PathBuf>,
375}
376
377/// Configuration for Linux bundles.
378///
379/// See more: <https://v2.tauri.app/reference/config/#linuxconfig>
380#[skip_serializing_none]
381#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
382#[cfg_attr(feature = "schema", derive(JsonSchema))]
383#[serde(rename_all = "camelCase", deny_unknown_fields)]
384pub struct LinuxConfig {
385  /// Configuration for the AppImage bundle.
386  #[serde(default)]
387  pub appimage: AppImageConfig,
388  /// Configuration for the Debian bundle.
389  #[serde(default)]
390  pub deb: DebConfig,
391  /// Configuration for the RPM bundle.
392  #[serde(default)]
393  pub rpm: RpmConfig,
394}
395
396/// Compression algorithms used when bundling RPM packages.
397#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
398#[cfg_attr(feature = "schema", derive(JsonSchema))]
399#[serde(rename_all = "camelCase", deny_unknown_fields, tag = "type")]
400#[non_exhaustive]
401pub enum RpmCompression {
402  /// Gzip compression
403  Gzip {
404    /// Gzip compression level
405    level: u32,
406  },
407  /// Zstd compression
408  Zstd {
409    /// Zstd compression level
410    level: i32,
411  },
412  /// Xz compression
413  Xz {
414    /// Xz compression level
415    level: u32,
416  },
417  /// Bzip2 compression
418  Bzip2 {
419    /// Bzip2 compression level
420    level: u32,
421  },
422  /// Disable compression
423  None,
424}
425
426/// Configuration for RPM bundles.
427#[skip_serializing_none]
428#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
429#[cfg_attr(feature = "schema", derive(JsonSchema))]
430#[serde(rename_all = "camelCase", deny_unknown_fields)]
431pub struct RpmConfig {
432  /// The list of RPM dependencies your application relies on.
433  pub depends: Option<Vec<String>>,
434  /// The list of RPM dependencies your application recommends.
435  pub recommends: Option<Vec<String>>,
436  /// The list of RPM dependencies your application provides.
437  pub provides: Option<Vec<String>>,
438  /// The list of RPM dependencies your application conflicts with. They must not be present
439  /// in order for the package to be installed.
440  pub conflicts: Option<Vec<String>>,
441  /// The list of RPM dependencies your application supersedes - if this package is installed,
442  /// packages listed as "obsoletes" will be automatically removed (if they are present).
443  pub obsoletes: Option<Vec<String>>,
444  /// The RPM release tag.
445  #[serde(default = "default_release")]
446  pub release: String,
447  /// The RPM epoch.
448  #[serde(default)]
449  pub epoch: u32,
450  /// The files to include on the package.
451  #[serde(default)]
452  pub files: HashMap<PathBuf, PathBuf>,
453  /// Path to a custom desktop file Handlebars template.
454  ///
455  /// Available variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.
456  #[serde(alias = "desktop-template")]
457  pub desktop_template: Option<PathBuf>,
458  /// Path to script that will be executed before the package is unpacked. See
459  /// <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>
460  #[serde(alias = "pre-install-script")]
461  pub pre_install_script: Option<PathBuf>,
462  /// Path to script that will be executed after the package is unpacked. See
463  /// <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>
464  #[serde(alias = "post-install-script")]
465  pub post_install_script: Option<PathBuf>,
466  /// Path to script that will be executed before the package is removed. See
467  /// <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>
468  #[serde(alias = "pre-remove-script")]
469  pub pre_remove_script: Option<PathBuf>,
470  /// Path to script that will be executed after the package is removed. See
471  /// <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>
472  #[serde(alias = "post-remove-script")]
473  pub post_remove_script: Option<PathBuf>,
474  /// Compression algorithm and level. Defaults to `Gzip` with level 6.
475  pub compression: Option<RpmCompression>,
476}
477
478impl Default for RpmConfig {
479  fn default() -> Self {
480    Self {
481      depends: None,
482      recommends: None,
483      provides: None,
484      conflicts: None,
485      obsoletes: None,
486      release: default_release(),
487      epoch: 0,
488      files: Default::default(),
489      desktop_template: None,
490      pre_install_script: None,
491      post_install_script: None,
492      pre_remove_script: None,
493      post_remove_script: None,
494      compression: None,
495    }
496  }
497}
498
499fn default_release() -> String {
500  "1".into()
501}
502
503/// Position coordinates struct.
504#[derive(Default, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
505#[cfg_attr(feature = "schema", derive(JsonSchema))]
506#[serde(rename_all = "camelCase", deny_unknown_fields)]
507pub struct Position {
508  /// X coordinate.
509  pub x: u32,
510  /// Y coordinate.
511  pub y: u32,
512}
513
514/// Position coordinates struct.
515#[derive(Default, Debug, PartialEq, Clone, Deserialize, Serialize)]
516#[cfg_attr(feature = "schema", derive(JsonSchema))]
517#[serde(rename_all = "camelCase", deny_unknown_fields)]
518pub struct LogicalPosition {
519  /// X coordinate.
520  pub x: f64,
521  /// Y coordinate.
522  pub y: f64,
523}
524
525/// Size of the window.
526#[derive(Default, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
527#[cfg_attr(feature = "schema", derive(JsonSchema))]
528#[serde(rename_all = "camelCase", deny_unknown_fields)]
529pub struct Size {
530  /// Width of the window.
531  pub width: u32,
532  /// Height of the window.
533  pub height: u32,
534}
535
536/// Configuration for Apple Disk Image (.dmg) bundles.
537///
538/// See more: <https://v2.tauri.app/reference/config/#dmgconfig>
539#[skip_serializing_none]
540#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
541#[cfg_attr(feature = "schema", derive(JsonSchema))]
542#[serde(rename_all = "camelCase", deny_unknown_fields)]
543pub struct DmgConfig {
544  /// Image to use as the background in dmg file. Accepted formats: `png`/`jpg`/`gif`.
545  pub background: Option<PathBuf>,
546  /// Position of volume window on screen.
547  pub window_position: Option<Position>,
548  /// Size of volume window.
549  #[serde(default = "dmg_window_size", alias = "window-size")]
550  pub window_size: Size,
551  /// Position of app file on window.
552  #[serde(default = "dmg_app_position", alias = "app-position")]
553  pub app_position: Position,
554  /// Position of application folder on window.
555  #[serde(
556    default = "dmg_application_folder_position",
557    alias = "application-folder-position"
558  )]
559  pub application_folder_position: Position,
560}
561
562impl Default for DmgConfig {
563  fn default() -> Self {
564    Self {
565      background: None,
566      window_position: None,
567      window_size: dmg_window_size(),
568      app_position: dmg_app_position(),
569      application_folder_position: dmg_application_folder_position(),
570    }
571  }
572}
573
574fn dmg_window_size() -> Size {
575  Size {
576    width: 660,
577    height: 400,
578  }
579}
580
581fn dmg_app_position() -> Position {
582  Position { x: 180, y: 170 }
583}
584
585fn dmg_application_folder_position() -> Position {
586  Position { x: 480, y: 170 }
587}
588
589fn de_macos_minimum_system_version<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
590where
591  D: Deserializer<'de>,
592{
593  let version = Option::<String>::deserialize(deserializer)?;
594  match version {
595    Some(v) if v.is_empty() => Ok(macos_minimum_system_version()),
596    e => Ok(e),
597  }
598}
599
600/// Configuration for the macOS bundles.
601///
602/// See more: <https://v2.tauri.app/reference/config/#macconfig>
603#[skip_serializing_none]
604#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
605#[cfg_attr(feature = "schema", derive(JsonSchema))]
606#[serde(rename_all = "camelCase", deny_unknown_fields)]
607pub struct MacConfig {
608  /// A list of strings indicating any macOS X frameworks that need to be bundled with the application.
609  ///
610  /// 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.
611  pub frameworks: Option<Vec<String>>,
612  /// The files to include in the application relative to the Contents directory.
613  #[serde(default)]
614  pub files: HashMap<PathBuf, PathBuf>,
615  /// The version of the build that identifies an iteration of the bundle.
616  ///
617  /// Translates to the bundle's CFBundleVersion property.
618  #[serde(alias = "bundle-version")]
619  pub bundle_version: Option<String>,
620  /// A version string indicating the minimum macOS X version that the bundled application supports. Defaults to `10.13`.
621  ///
622  /// Setting it to `null` completely removes the `LSMinimumSystemVersion` field on the bundle's `Info.plist`
623  /// and the `MACOSX_DEPLOYMENT_TARGET` environment variable.
624  ///
625  /// An empty string is considered an invalid value so the default value is used.
626  #[serde(
627    deserialize_with = "de_macos_minimum_system_version",
628    default = "macos_minimum_system_version",
629    alias = "minimum-system-version"
630  )]
631  pub minimum_system_version: Option<String>,
632  /// Allows your application to communicate with the outside world.
633  /// It should be a lowercase, without port and protocol domain name.
634  #[serde(alias = "exception-domain")]
635  pub exception_domain: Option<String>,
636  /// Identity to use for code signing.
637  #[serde(alias = "signing-identity")]
638  pub signing_identity: Option<String>,
639  /// Whether the codesign should enable [hardened runtime] (for executables) or not.
640  ///
641  /// [hardened runtime]: <https://developer.apple.com/documentation/security/hardened_runtime>
642  #[serde(alias = "hardened-runtime", default = "default_true")]
643  pub hardened_runtime: bool,
644  /// Provider short name for notarization.
645  #[serde(alias = "provider-short-name")]
646  pub provider_short_name: Option<String>,
647  /// Path to the entitlements file.
648  pub entitlements: Option<String>,
649  /// DMG-specific settings.
650  #[serde(default)]
651  pub dmg: DmgConfig,
652}
653
654impl Default for MacConfig {
655  fn default() -> Self {
656    Self {
657      frameworks: None,
658      files: HashMap::new(),
659      bundle_version: None,
660      minimum_system_version: macos_minimum_system_version(),
661      exception_domain: None,
662      signing_identity: None,
663      hardened_runtime: true,
664      provider_short_name: None,
665      entitlements: None,
666      dmg: Default::default(),
667    }
668  }
669}
670
671fn macos_minimum_system_version() -> Option<String> {
672  Some("10.13".into())
673}
674
675fn ios_minimum_system_version() -> String {
676  "13.0".into()
677}
678
679/// Configuration for a target language for the WiX build.
680///
681/// See more: <https://v2.tauri.app/reference/config/#wixlanguageconfig>
682#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
683#[cfg_attr(feature = "schema", derive(JsonSchema))]
684#[serde(rename_all = "camelCase", deny_unknown_fields)]
685pub struct WixLanguageConfig {
686  /// The path to a locale (`.wxl`) file. See <https://wixtoolset.org/documentation/manual/v3/howtos/ui_and_localization/build_a_localized_version.html>.
687  #[serde(alias = "locale-path")]
688  pub locale_path: Option<String>,
689}
690
691/// The languages to build using WiX.
692#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
693#[cfg_attr(feature = "schema", derive(JsonSchema))]
694#[serde(untagged)]
695pub enum WixLanguage {
696  /// A single language to build, without configuration.
697  One(String),
698  /// A list of languages to build, without configuration.
699  List(Vec<String>),
700  /// A map of languages and its configuration.
701  Localized(HashMap<String, WixLanguageConfig>),
702}
703
704impl Default for WixLanguage {
705  fn default() -> Self {
706    Self::One("en-US".into())
707  }
708}
709
710/// Configuration for the MSI bundle using WiX.
711///
712/// See more: <https://v2.tauri.app/reference/config/#wixconfig>
713#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
714#[cfg_attr(feature = "schema", derive(JsonSchema))]
715#[serde(rename_all = "camelCase", deny_unknown_fields)]
716pub struct WixConfig {
717  /// MSI installer version in the format `major.minor.patch.build` (build is optional).
718  ///
719  /// Because a valid version is required for MSI installer, it will be derived from [`Config::version`] if this field is not set.
720  ///
721  /// 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.
722  /// The third and foruth fields have a maximum value of 65,535.
723  ///
724  /// See <https://learn.microsoft.com/en-us/windows/win32/msi/productversion> for more info.
725  pub version: Option<String>,
726  /// A GUID upgrade code for MSI installer. This code **_must stay the same across all of your updates_**,
727  /// otherwise, Windows will treat your update as a different app and your users will have duplicate versions of your app.
728  ///
729  /// By default, tauri generates this code by generating a Uuid v5 using the string `<productName>.exe.app.x64` in the DNS namespace.
730  /// You can use Tauri's CLI to generate and print this code for you, run `tauri inspect wix-upgrade-code`.
731  ///
732  /// It is recommended that you set this value in your tauri config file to avoid accidental changes in your upgrade code
733  /// whenever you want to change your product name.
734  #[serde(alias = "upgrade-code")]
735  pub upgrade_code: Option<uuid::Uuid>,
736  /// The installer languages to build. See <https://docs.microsoft.com/en-us/windows/win32/msi/localizing-the-error-and-actiontext-tables>.
737  #[serde(default)]
738  pub language: WixLanguage,
739  /// A custom .wxs template to use.
740  pub template: Option<PathBuf>,
741  /// A list of paths to .wxs files with WiX fragments to use.
742  #[serde(default, alias = "fragment-paths")]
743  pub fragment_paths: Vec<PathBuf>,
744  /// The ComponentGroup element ids you want to reference from the fragments.
745  #[serde(default, alias = "component-group-refs")]
746  pub component_group_refs: Vec<String>,
747  /// The Component element ids you want to reference from the fragments.
748  #[serde(default, alias = "component-refs")]
749  pub component_refs: Vec<String>,
750  /// The FeatureGroup element ids you want to reference from the fragments.
751  #[serde(default, alias = "feature-group-refs")]
752  pub feature_group_refs: Vec<String>,
753  /// The Feature element ids you want to reference from the fragments.
754  #[serde(default, alias = "feature-refs")]
755  pub feature_refs: Vec<String>,
756  /// The Merge element ids you want to reference from the fragments.
757  #[serde(default, alias = "merge-refs")]
758  pub merge_refs: Vec<String>,
759  /// Create an elevated update task within Windows Task Scheduler.
760  #[serde(default, alias = "enable-elevated-update-task")]
761  pub enable_elevated_update_task: bool,
762  /// Path to a bitmap file to use as the installation user interface banner.
763  /// This bitmap will appear at the top of all but the first page of the installer.
764  ///
765  /// The required dimensions are 493px × 58px.
766  #[serde(alias = "banner-path")]
767  pub banner_path: Option<PathBuf>,
768  /// Path to a bitmap file to use on the installation user interface dialogs.
769  /// It is used on the welcome and completion dialogs.
770  ///
771  /// The required dimensions are 493px × 312px.
772  #[serde(alias = "dialog-image-path")]
773  pub dialog_image_path: Option<PathBuf>,
774}
775
776/// Compression algorithms used in the NSIS installer.
777///
778/// See <https://nsis.sourceforge.io/Reference/SetCompressor>
779#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
780#[cfg_attr(feature = "schema", derive(JsonSchema))]
781#[serde(rename_all = "camelCase", deny_unknown_fields)]
782pub enum NsisCompression {
783  /// ZLIB uses the deflate algorithm, it is a quick and simple method. With the default compression level it uses about 300 KB of memory.
784  Zlib,
785  /// 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.
786  Bzip2,
787  /// 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.
788  Lzma,
789  /// Disable compression
790  None,
791}
792
793impl Default for NsisCompression {
794  fn default() -> Self {
795    Self::Lzma
796  }
797}
798
799/// Install Modes for the NSIS installer.
800#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
801#[serde(rename_all = "camelCase", deny_unknown_fields)]
802#[cfg_attr(feature = "schema", derive(JsonSchema))]
803pub enum NSISInstallerMode {
804  /// Default mode for the installer.
805  ///
806  /// Install the app by default in a directory that doesn't require Administrator access.
807  ///
808  /// Installer metadata will be saved under the `HKCU` registry path.
809  CurrentUser,
810  /// Install the app by default in the `Program Files` folder directory requires Administrator
811  /// access for the installation.
812  ///
813  /// Installer metadata will be saved under the `HKLM` registry path.
814  PerMachine,
815  /// Combines both modes and allows the user to choose at install time
816  /// whether to install for the current user or per machine. Note that this mode
817  /// will require Administrator access even if the user wants to install it for the current user only.
818  ///
819  /// Installer metadata will be saved under the `HKLM` or `HKCU` registry path based on the user's choice.
820  Both,
821}
822
823impl Default for NSISInstallerMode {
824  fn default() -> Self {
825    Self::CurrentUser
826  }
827}
828
829/// Configuration for the Installer bundle using NSIS.
830#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
831#[cfg_attr(feature = "schema", derive(JsonSchema))]
832#[serde(rename_all = "camelCase", deny_unknown_fields)]
833pub struct NsisConfig {
834  /// A custom .nsi template to use.
835  pub template: Option<PathBuf>,
836  /// The path to a bitmap file to display on the header of installers pages.
837  ///
838  /// The recommended dimensions are 150px x 57px.
839  #[serde(alias = "header-image")]
840  pub header_image: Option<PathBuf>,
841  /// The path to a bitmap file for the Welcome page and the Finish page.
842  ///
843  /// The recommended dimensions are 164px x 314px.
844  #[serde(alias = "sidebar-image")]
845  pub sidebar_image: Option<PathBuf>,
846  /// The path to an icon file used as the installer icon.
847  #[serde(alias = "install-icon")]
848  pub installer_icon: Option<PathBuf>,
849  /// Whether the installation will be for all users or just the current user.
850  #[serde(default, alias = "install-mode")]
851  pub install_mode: NSISInstallerMode,
852  /// A list of installer languages.
853  /// By default the OS language is used. If the OS language is not in the list of languages, the first language will be used.
854  /// To allow the user to select the language, set `display_language_selector` to `true`.
855  ///
856  /// See <https://github.com/kichik/nsis/tree/9465c08046f00ccb6eda985abbdbf52c275c6c4d/Contrib/Language%20files> for the complete list of languages.
857  pub languages: Option<Vec<String>>,
858  /// A key-value pair where the key is the language and the
859  /// value is the path to a custom `.nsh` file that holds the translated text for tauri's custom messages.
860  ///
861  /// See <https://github.com/tauri-apps/tauri/blob/dev/crates/tauri-bundler/src/bundle/windows/nsis/languages/English.nsh> for an example `.nsh` file.
862  ///
863  /// **Note**: the key must be a valid NSIS language and it must be added to [`NsisConfig`] languages array,
864  pub custom_language_files: Option<HashMap<String, PathBuf>>,
865  /// Whether to display a language selector dialog before the installer and uninstaller windows are rendered or not.
866  /// By default the OS language is selected, with a fallback to the first language in the `languages` array.
867  #[serde(default, alias = "display-language-selector")]
868  pub display_language_selector: bool,
869  /// Set the compression algorithm used to compress files in the installer.
870  ///
871  /// See <https://nsis.sourceforge.io/Reference/SetCompressor>
872  #[serde(default)]
873  pub compression: NsisCompression,
874  /// Set the folder name for the start menu shortcut.
875  ///
876  /// Use this option if you have multiple apps and wish to group their shortcuts under one folder
877  /// or if you generally prefer to set your shortcut inside a folder.
878  ///
879  /// Examples:
880  /// - `AwesomePublisher`, shortcut will be placed in `%AppData%\Microsoft\Windows\Start Menu\Programs\AwesomePublisher\<your-app>.lnk`
881  /// - If unset, shortcut will be placed in `%AppData%\Microsoft\Windows\Start Menu\Programs\<your-app>.lnk`
882  #[serde(alias = "start-menu-folder")]
883  pub start_menu_folder: Option<String>,
884  /// A path to a `.nsh` file that contains special NSIS macros to be hooked into the
885  /// main installer.nsi script.
886  ///
887  /// Supported hooks are:
888  /// - `NSIS_HOOK_PREINSTALL`: This hook runs before copying files, setting registry key values and creating shortcuts.
889  /// - `NSIS_HOOK_POSTINSTALL`: This hook runs after the installer has finished copying all files, setting the registry keys and created shortcuts.
890  /// - `NSIS_HOOK_PREUNINSTALL`: This hook runs before removing any files, registry keys and shortcuts.
891  /// - `NSIS_HOOK_POSTUNINSTALL`: This hook runs after files, registry keys and shortcuts have been removed.
892  ///
893  ///
894  /// ### Example
895  ///
896  /// ```nsh
897  /// !macro NSIS_HOOK_PREINSTALL
898  ///   MessageBox MB_OK "PreInstall"
899  /// !macroend
900  ///
901  /// !macro NSIS_HOOK_POSTINSTALL
902  ///   MessageBox MB_OK "PostInstall"
903  /// !macroend
904  ///
905  /// !macro NSIS_HOOK_PREUNINSTALL
906  ///   MessageBox MB_OK "PreUnInstall"
907  /// !macroend
908  ///
909  /// !macro NSIS_HOOK_POSTUNINSTALL
910  ///   MessageBox MB_OK "PostUninstall"
911  /// !macroend
912  ///
913  /// ```
914  #[serde(alias = "installer-hooks")]
915  pub installer_hooks: Option<PathBuf>,
916  /// Try to ensure that the WebView2 version is equal to or newer than this version,
917  /// if the user's WebView2 is older than this version,
918  /// the installer will try to trigger a WebView2 update.
919  #[serde(alias = "minimum-webview2-version")]
920  pub minimum_webview2_version: Option<String>,
921}
922
923/// Install modes for the Webview2 runtime.
924/// Note that for the updater bundle [`Self::DownloadBootstrapper`] is used.
925///
926/// For more information see <https://v2.tauri.app/distribute/windows-installer/#webview2-installation-options>.
927#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
928#[serde(tag = "type", rename_all = "camelCase", deny_unknown_fields)]
929#[cfg_attr(feature = "schema", derive(JsonSchema))]
930pub enum WebviewInstallMode {
931  /// Do not install the Webview2 as part of the Windows Installer.
932  Skip,
933  /// Download the bootstrapper and run it.
934  /// Requires an internet connection.
935  /// Results in a smaller installer size, but is not recommended on Windows 7.
936  DownloadBootstrapper {
937    /// Instructs the installer to run the bootstrapper in silent mode. Defaults to `true`.
938    #[serde(default = "default_true")]
939    silent: bool,
940  },
941  /// Embed the bootstrapper and run it.
942  /// Requires an internet connection.
943  /// Increases the installer size by around 1.8MB, but offers better support on Windows 7.
944  EmbedBootstrapper {
945    /// Instructs the installer to run the bootstrapper in silent mode. Defaults to `true`.
946    #[serde(default = "default_true")]
947    silent: bool,
948  },
949  /// Embed the offline installer and run it.
950  /// Does not require an internet connection.
951  /// Increases the installer size by around 127MB.
952  OfflineInstaller {
953    /// Instructs the installer to run the installer in silent mode. Defaults to `true`.
954    #[serde(default = "default_true")]
955    silent: bool,
956  },
957  /// Embed a fixed webview2 version and use it at runtime.
958  /// Increases the installer size by around 180MB.
959  FixedRuntime {
960    /// The path to the fixed runtime to use.
961    ///
962    /// The fixed version can be downloaded [on the official website](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section).
963    /// The `.cab` file must be extracted to a folder and this folder path must be defined on this field.
964    path: PathBuf,
965  },
966}
967
968impl Default for WebviewInstallMode {
969  fn default() -> Self {
970    Self::DownloadBootstrapper { silent: true }
971  }
972}
973
974/// Custom Signing Command configuration.
975#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
976#[cfg_attr(feature = "schema", derive(JsonSchema))]
977#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
978pub enum CustomSignCommandConfig {
979  /// A string notation of the script to execute.
980  ///
981  /// "%1" will be replaced with the path to the binary to be signed.
982  ///
983  /// This is a simpler notation for the command.
984  /// Tauri will split the string with `' '` and use the first element as the command name and the rest as arguments.
985  ///
986  /// If you need to use whitespace in the command or arguments, use the object notation [`Self::ScriptWithOptions`].
987  Command(String),
988  /// An object notation of the command.
989  ///
990  /// This is more complex notation for the command but
991  /// this allows you to use whitespace in the command and arguments.
992  CommandWithOptions {
993    /// The command to run to sign the binary.
994    cmd: String,
995    /// The arguments to pass to the command.
996    ///
997    /// "%1" will be replaced with the path to the binary to be signed.
998    args: Vec<String>,
999  },
1000}
1001
1002/// Windows bundler configuration.
1003///
1004/// See more: <https://v2.tauri.app/reference/config/#windowsconfig>
1005#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1006#[cfg_attr(feature = "schema", derive(JsonSchema))]
1007#[serde(rename_all = "camelCase", deny_unknown_fields)]
1008pub struct WindowsConfig {
1009  /// Specifies the file digest algorithm to use for creating file signatures.
1010  /// Required for code signing. SHA-256 is recommended.
1011  #[serde(alias = "digest-algorithm")]
1012  pub digest_algorithm: Option<String>,
1013  /// Specifies the SHA1 hash of the signing certificate.
1014  #[serde(alias = "certificate-thumbprint")]
1015  pub certificate_thumbprint: Option<String>,
1016  /// Server to use during timestamping.
1017  #[serde(alias = "timestamp-url")]
1018  pub timestamp_url: Option<String>,
1019  /// Whether to use Time-Stamp Protocol (TSP, a.k.a. RFC 3161) for the timestamp server. Your code signing provider may
1020  /// use a TSP timestamp server, like e.g. SSL.com does. If so, enable TSP by setting to true.
1021  #[serde(default)]
1022  pub tsp: bool,
1023  /// The installation mode for the Webview2 runtime.
1024  #[serde(default, alias = "webview-install-mode")]
1025  pub webview_install_mode: WebviewInstallMode,
1026  /// Validates a second app installation, blocking the user from installing an older version if set to `false`.
1027  ///
1028  /// For instance, if `1.2.1` is installed, the user won't be able to install app version `1.2.0` or `1.1.5`.
1029  ///
1030  /// The default value of this flag is `true`.
1031  #[serde(default = "default_true", alias = "allow-downgrades")]
1032  pub allow_downgrades: bool,
1033  /// Configuration for the MSI generated with WiX.
1034  pub wix: Option<WixConfig>,
1035  /// Configuration for the installer generated with NSIS.
1036  pub nsis: Option<NsisConfig>,
1037  /// Specify a custom command to sign the binaries.
1038  /// This command needs to have a `%1` in args which is just a placeholder for the binary path,
1039  /// which we will detect and replace before calling the command.
1040  ///
1041  /// By Default we use `signtool.exe` which can be found only on Windows so
1042  /// if you are on another platform and want to cross-compile and sign you will
1043  /// need to use another tool like `osslsigncode`.
1044  #[serde(alias = "sign-command")]
1045  pub sign_command: Option<CustomSignCommandConfig>,
1046}
1047
1048impl Default for WindowsConfig {
1049  fn default() -> Self {
1050    Self {
1051      digest_algorithm: None,
1052      certificate_thumbprint: None,
1053      timestamp_url: None,
1054      tsp: false,
1055      webview_install_mode: Default::default(),
1056      allow_downgrades: true,
1057      wix: None,
1058      nsis: None,
1059      sign_command: None,
1060    }
1061  }
1062}
1063
1064/// macOS-only. Corresponds to CFBundleTypeRole
1065#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1066#[cfg_attr(feature = "schema", derive(JsonSchema))]
1067pub enum BundleTypeRole {
1068  /// CFBundleTypeRole.Editor. Files can be read and edited.
1069  #[default]
1070  Editor,
1071  /// CFBundleTypeRole.Viewer. Files can be read.
1072  Viewer,
1073  /// CFBundleTypeRole.Shell
1074  Shell,
1075  /// CFBundleTypeRole.QLGenerator
1076  QLGenerator,
1077  /// CFBundleTypeRole.None
1078  None,
1079}
1080
1081impl Display for BundleTypeRole {
1082  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1083    match self {
1084      Self::Editor => write!(f, "Editor"),
1085      Self::Viewer => write!(f, "Viewer"),
1086      Self::Shell => write!(f, "Shell"),
1087      Self::QLGenerator => write!(f, "QLGenerator"),
1088      Self::None => write!(f, "None"),
1089    }
1090  }
1091}
1092
1093// Issue #13159 - Missing the LSHandlerRank and Apple warns after uploading to App Store Connect.
1094// https://github.com/tauri-apps/tauri/issues/13159
1095/// Corresponds to LSHandlerRank
1096#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1097#[cfg_attr(feature = "schema", derive(JsonSchema))]
1098pub enum HandlerRank {
1099  /// LSHandlerRank.Default. This app is an opener of files of this type; this value is also used if no rank is specified.
1100  #[default]
1101  Default,
1102  /// LSHandlerRank.Owner. This app is the primary creator of files of this type.
1103  Owner,
1104  /// LSHandlerRank.Alternate. This app is a secondary viewer of files of this type.
1105  Alternate,
1106  /// LSHandlerRank.None. This app is never selected to open files of this type, but it accepts drops of files of this type.
1107  None,
1108}
1109
1110impl Display for HandlerRank {
1111  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1112    match self {
1113      Self::Default => write!(f, "Default"),
1114      Self::Owner => write!(f, "Owner"),
1115      Self::Alternate => write!(f, "Alternate"),
1116      Self::None => write!(f, "None"),
1117    }
1118  }
1119}
1120
1121/// An extension for a [`FileAssociation`].
1122///
1123/// A leading `.` is automatically stripped.
1124#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
1125#[cfg_attr(feature = "schema", derive(JsonSchema))]
1126pub struct AssociationExt(pub String);
1127
1128impl fmt::Display for AssociationExt {
1129  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1130    write!(f, "{}", self.0)
1131  }
1132}
1133
1134impl<'d> serde::Deserialize<'d> for AssociationExt {
1135  fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {
1136    let ext = String::deserialize(deserializer)?;
1137    if let Some(ext) = ext.strip_prefix('.') {
1138      Ok(AssociationExt(ext.into()))
1139    } else {
1140      Ok(AssociationExt(ext))
1141    }
1142  }
1143}
1144
1145/// File association
1146#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1147#[cfg_attr(feature = "schema", derive(JsonSchema))]
1148#[serde(rename_all = "camelCase", deny_unknown_fields)]
1149pub struct FileAssociation {
1150  /// File extensions to associate with this app. e.g. 'png'
1151  pub ext: Vec<AssociationExt>,
1152  /// The name. Maps to `CFBundleTypeName` on macOS. Default to `ext[0]`
1153  pub name: Option<String>,
1154  /// The association description. Windows-only. It is displayed on the `Type` column on Windows Explorer.
1155  pub description: Option<String>,
1156  /// The app's role with respect to the type. Maps to `CFBundleTypeRole` on macOS.
1157  #[serde(default)]
1158  pub role: BundleTypeRole,
1159  /// The mime-type e.g. 'image/png' or 'text/plain'. Linux-only.
1160  #[serde(alias = "mime-type")]
1161  pub mime_type: Option<String>,
1162  /// The ranking of this app among apps that declare themselves as editors or viewers of the given file type.  Maps to `LSHandlerRank` on macOS.
1163  #[serde(default)]
1164  pub rank: HandlerRank,
1165}
1166
1167/// Deep link protocol configuration.
1168#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1169#[cfg_attr(feature = "schema", derive(JsonSchema))]
1170#[serde(rename_all = "camelCase", deny_unknown_fields)]
1171pub struct DeepLinkProtocol {
1172  /// URL schemes to associate with this app without `://`. For example `my-app`
1173  pub schemes: Vec<String>,
1174  /// The protocol name. **macOS-only** and maps to `CFBundleTypeName`. Defaults to `<bundle-id>.<schemes[0]>`
1175  pub name: Option<String>,
1176  /// The app's role for these schemes. **macOS-only** and maps to `CFBundleTypeRole`.
1177  #[serde(default)]
1178  pub role: BundleTypeRole,
1179}
1180
1181/// Definition for bundle resources.
1182/// Can be either a list of paths to include or a map of source to target paths.
1183#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1184#[cfg_attr(feature = "schema", derive(JsonSchema))]
1185#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
1186pub enum BundleResources {
1187  /// A list of paths to include.
1188  List(Vec<String>),
1189  /// A map of source to target paths.
1190  Map(HashMap<String, String>),
1191}
1192
1193impl BundleResources {
1194  /// Adds a path to the resource collection.
1195  pub fn push(&mut self, path: impl Into<String>) {
1196    match self {
1197      Self::List(l) => l.push(path.into()),
1198      Self::Map(l) => {
1199        let path = path.into();
1200        l.insert(path.clone(), path);
1201      }
1202    }
1203  }
1204}
1205
1206/// Updater type
1207#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1208#[cfg_attr(feature = "schema", derive(JsonSchema))]
1209#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
1210pub enum Updater {
1211  /// Generates lagacy zipped v1 compatible updaters
1212  String(V1Compatible),
1213  /// Produce updaters and their signatures or not
1214  // Can't use untagged on enum field here: https://github.com/GREsau/schemars/issues/222
1215  Bool(bool),
1216}
1217
1218impl Default for Updater {
1219  fn default() -> Self {
1220    Self::Bool(false)
1221  }
1222}
1223
1224/// Generates lagacy zipped v1 compatible updaters
1225#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1226#[cfg_attr(feature = "schema", derive(JsonSchema))]
1227#[serde(rename_all = "camelCase", deny_unknown_fields)]
1228pub enum V1Compatible {
1229  /// Generates lagacy zipped v1 compatible updaters
1230  V1Compatible,
1231}
1232
1233/// Configuration for tauri-bundler.
1234///
1235/// See more: <https://v2.tauri.app/reference/config/#bundleconfig>
1236#[skip_serializing_none]
1237#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1238#[cfg_attr(feature = "schema", derive(JsonSchema))]
1239#[serde(rename_all = "camelCase", deny_unknown_fields)]
1240pub struct BundleConfig {
1241  /// Whether Tauri should bundle your application or just output the executable.
1242  #[serde(default)]
1243  pub active: bool,
1244  /// The bundle targets, currently supports ["deb", "rpm", "appimage", "nsis", "msi", "app", "dmg"] or "all".
1245  #[serde(default)]
1246  pub targets: BundleTarget,
1247  #[serde(default)]
1248  /// Produce updaters and their signatures or not
1249  pub create_updater_artifacts: Updater,
1250  /// The application's publisher. Defaults to the second element in the identifier string.
1251  ///
1252  /// Currently maps to the Manufacturer property of the Windows Installer
1253  /// and the Maintainer field of debian packages if the Cargo.toml does not have the authors field.
1254  pub publisher: Option<String>,
1255  /// A url to the home page of your application. If unset, will
1256  /// fallback to `homepage` defined in `Cargo.toml`.
1257  ///
1258  /// Supported bundle targets: `deb`, `rpm`, `nsis` and `msi`.
1259  pub homepage: Option<String>,
1260  /// The app's icons
1261  #[serde(default)]
1262  pub icon: Vec<String>,
1263  /// App resources to bundle.
1264  /// Each resource is a path to a file or directory.
1265  /// Glob patterns are supported.
1266  pub resources: Option<BundleResources>,
1267  /// A copyright string associated with your application.
1268  pub copyright: Option<String>,
1269  /// The package's license identifier to be included in the appropriate bundles.
1270  /// If not set, defaults to the license from the Cargo.toml file.
1271  pub license: Option<String>,
1272  /// The path to the license file to be included in the appropriate bundles.
1273  #[serde(alias = "license-file")]
1274  pub license_file: Option<PathBuf>,
1275  /// The application kind.
1276  ///
1277  /// Should be one of the following:
1278  /// 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.
1279  pub category: Option<String>,
1280  /// File associations to application.
1281  pub file_associations: Option<Vec<FileAssociation>>,
1282  /// A short description of your application.
1283  #[serde(alias = "short-description")]
1284  pub short_description: Option<String>,
1285  /// A longer, multi-line description of the application.
1286  #[serde(alias = "long-description")]
1287  pub long_description: Option<String>,
1288  /// Whether to use the project's `target` directory, for caching build tools (e.g., Wix and NSIS) when building this application. Defaults to `false`.
1289  ///
1290  /// If true, tools will be cached in `target/.tauri/`.
1291  /// If false, tools will be cached in the current user's platform-specific cache directory.
1292  ///
1293  /// 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),
1294  /// because the Window system's app data directory is restricted.
1295  #[serde(default, alias = "use-local-tools-dir")]
1296  pub use_local_tools_dir: bool,
1297  /// A list of—either absolute or relative—paths to binaries to embed with your application.
1298  ///
1299  /// Note that Tauri will look for system-specific binaries following the pattern "binary-name{-target-triple}{.system-extension}".
1300  ///
1301  /// E.g. for the external binary "my-binary", Tauri looks for:
1302  ///
1303  /// - "my-binary-x86_64-pc-windows-msvc.exe" for Windows
1304  /// - "my-binary-x86_64-apple-darwin" for macOS
1305  /// - "my-binary-x86_64-unknown-linux-gnu" for Linux
1306  ///
1307  /// so don't forget to provide binaries for all targeted platforms.
1308  #[serde(alias = "external-bin")]
1309  pub external_bin: Option<Vec<String>>,
1310  /// Configuration for the Windows bundles.
1311  #[serde(default)]
1312  pub windows: WindowsConfig,
1313  /// Configuration for the Linux bundles.
1314  #[serde(default)]
1315  pub linux: LinuxConfig,
1316  /// Configuration for the macOS bundles.
1317  #[serde(rename = "macOS", alias = "macos", default)]
1318  pub macos: MacConfig,
1319  /// iOS configuration.
1320  #[serde(rename = "iOS", alias = "ios", default)]
1321  pub ios: IosConfig,
1322  /// Android configuration.
1323  #[serde(default)]
1324  pub android: AndroidConfig,
1325}
1326
1327/// A tuple struct of RGBA colors. Each value has minimum of 0 and maximum of 255.
1328#[derive(Debug, PartialEq, Eq, Serialize, Default, Clone, Copy)]
1329#[serde(rename_all = "camelCase", deny_unknown_fields)]
1330pub struct Color(pub u8, pub u8, pub u8, pub u8);
1331
1332impl From<Color> for (u8, u8, u8, u8) {
1333  fn from(value: Color) -> Self {
1334    (value.0, value.1, value.2, value.3)
1335  }
1336}
1337
1338impl From<Color> for (u8, u8, u8) {
1339  fn from(value: Color) -> Self {
1340    (value.0, value.1, value.2)
1341  }
1342}
1343
1344impl From<(u8, u8, u8, u8)> for Color {
1345  fn from(value: (u8, u8, u8, u8)) -> Self {
1346    Color(value.0, value.1, value.2, value.3)
1347  }
1348}
1349
1350impl From<(u8, u8, u8)> for Color {
1351  fn from(value: (u8, u8, u8)) -> Self {
1352    Color(value.0, value.1, value.2, 255)
1353  }
1354}
1355
1356impl From<Color> for [u8; 4] {
1357  fn from(value: Color) -> Self {
1358    [value.0, value.1, value.2, value.3]
1359  }
1360}
1361
1362impl From<Color> for [u8; 3] {
1363  fn from(value: Color) -> Self {
1364    [value.0, value.1, value.2]
1365  }
1366}
1367
1368impl From<[u8; 4]> for Color {
1369  fn from(value: [u8; 4]) -> Self {
1370    Color(value[0], value[1], value[2], value[3])
1371  }
1372}
1373
1374impl From<[u8; 3]> for Color {
1375  fn from(value: [u8; 3]) -> Self {
1376    Color(value[0], value[1], value[2], 255)
1377  }
1378}
1379
1380impl FromStr for Color {
1381  type Err = String;
1382  fn from_str(mut color: &str) -> Result<Self, Self::Err> {
1383    color = color.trim().strip_prefix('#').unwrap_or(color);
1384    let color = match color.len() {
1385      // TODO: use repeat_n once our MSRV is bumped to 1.82
1386      3 => color.chars()
1387            .flat_map(|c| std::iter::repeat(c).take(2))
1388            .chain(std::iter::repeat('f').take(2))
1389            .collect(),
1390      6 => format!("{color}FF"),
1391      8 => color.to_string(),
1392      _ => return Err("Invalid hex color length, must be either 3, 6 or 8, for example: #fff, #ffffff, or #ffffffff".into()),
1393    };
1394
1395    let r = u8::from_str_radix(&color[0..2], 16).map_err(|e| e.to_string())?;
1396    let g = u8::from_str_radix(&color[2..4], 16).map_err(|e| e.to_string())?;
1397    let b = u8::from_str_radix(&color[4..6], 16).map_err(|e| e.to_string())?;
1398    let a = u8::from_str_radix(&color[6..8], 16).map_err(|e| e.to_string())?;
1399
1400    Ok(Color(r, g, b, a))
1401  }
1402}
1403
1404fn default_alpha() -> u8 {
1405  255
1406}
1407
1408#[derive(Deserialize)]
1409#[cfg_attr(feature = "schema", derive(JsonSchema))]
1410#[serde(untagged)]
1411enum InnerColor {
1412  /// Color hex string, for example: #fff, #ffffff, or #ffffffff.
1413  String(String),
1414  /// Array of RGB colors. Each value has minimum of 0 and maximum of 255.
1415  Rgb((u8, u8, u8)),
1416  /// Array of RGBA colors. Each value has minimum of 0 and maximum of 255.
1417  Rgba((u8, u8, u8, u8)),
1418  /// Object of red, green, blue, alpha color values. Each value has minimum of 0 and maximum of 255.
1419  RgbaObject {
1420    red: u8,
1421    green: u8,
1422    blue: u8,
1423    #[serde(default = "default_alpha")]
1424    alpha: u8,
1425  },
1426}
1427
1428impl<'de> Deserialize<'de> for Color {
1429  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1430  where
1431    D: Deserializer<'de>,
1432  {
1433    let color = InnerColor::deserialize(deserializer)?;
1434    let color = match color {
1435      InnerColor::String(string) => string.parse().map_err(serde::de::Error::custom)?,
1436      InnerColor::Rgb(rgb) => Color(rgb.0, rgb.1, rgb.2, 255),
1437      InnerColor::Rgba(rgb) => rgb.into(),
1438      InnerColor::RgbaObject {
1439        red,
1440        green,
1441        blue,
1442        alpha,
1443      } => Color(red, green, blue, alpha),
1444    };
1445
1446    Ok(color)
1447  }
1448}
1449
1450#[cfg(feature = "schema")]
1451impl schemars::JsonSchema for Color {
1452  fn schema_name() -> String {
1453    "Color".to_string()
1454  }
1455
1456  fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
1457    let mut schema = schemars::schema_for!(InnerColor).schema;
1458    schema.metadata = None; // Remove `title: InnerColor` from schema
1459
1460    // add hex color pattern validation
1461    let any_of = schema.subschemas().any_of.as_mut().unwrap();
1462    let schemars::schema::Schema::Object(str_schema) = any_of.first_mut().unwrap() else {
1463      unreachable!()
1464    };
1465    str_schema.string().pattern = Some("^#?([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$".into());
1466
1467    schema.into()
1468  }
1469}
1470
1471/// Background throttling policy.
1472#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1473#[cfg_attr(feature = "schema", derive(JsonSchema))]
1474#[serde(rename_all = "camelCase", deny_unknown_fields)]
1475pub enum BackgroundThrottlingPolicy {
1476  /// A policy where background throttling is disabled
1477  Disabled,
1478  /// 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.
1479  Suspend,
1480  /// A policy where a web view that’s not in a window limits processing, but does not fully suspend tasks.
1481  Throttle,
1482}
1483
1484/// The window effects configuration object
1485#[skip_serializing_none]
1486#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default)]
1487#[cfg_attr(feature = "schema", derive(JsonSchema))]
1488#[serde(rename_all = "camelCase", deny_unknown_fields)]
1489pub struct WindowEffectsConfig {
1490  /// List of Window effects to apply to the Window.
1491  /// Conflicting effects will apply the first one and ignore the rest.
1492  pub effects: Vec<WindowEffect>,
1493  /// Window effect state **macOS Only**
1494  pub state: Option<WindowEffectState>,
1495  /// Window effect corner radius **macOS Only**
1496  pub radius: Option<f64>,
1497  /// Window effect color. Affects [`WindowEffect::Blur`] and [`WindowEffect::Acrylic`] only
1498  /// on Windows 10 v1903+. Doesn't have any effect on Windows 7 or Windows 11.
1499  pub color: Option<Color>,
1500}
1501
1502/// Enable prevent overflow with a margin
1503/// so that the window's size + this margin won't overflow the workarea
1504#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default)]
1505#[cfg_attr(feature = "schema", derive(JsonSchema))]
1506#[serde(rename_all = "camelCase", deny_unknown_fields)]
1507pub struct PreventOverflowMargin {
1508  /// Horizontal margin in physical unit
1509  pub width: u32,
1510  /// Vertical margin in physical unit
1511  pub height: u32,
1512}
1513
1514/// Prevent overflow with a margin
1515#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1516#[cfg_attr(feature = "schema", derive(JsonSchema))]
1517#[serde(untagged)]
1518pub enum PreventOverflowConfig {
1519  /// Enable prevent overflow or not
1520  Enable(bool),
1521  /// Enable prevent overflow with a margin
1522  /// so that the window's size + this margin won't overflow the workarea
1523  Margin(PreventOverflowMargin),
1524}
1525
1526/// The window configuration object.
1527///
1528/// See more: <https://v2.tauri.app/reference/config/#windowconfig>
1529#[skip_serializing_none]
1530#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
1531#[cfg_attr(feature = "schema", derive(JsonSchema))]
1532#[serde(rename_all = "camelCase", deny_unknown_fields)]
1533pub struct WindowConfig {
1534  /// The window identifier. It must be alphanumeric.
1535  #[serde(default = "default_window_label")]
1536  pub label: String,
1537  /// Whether Tauri should create this window at app startup or not.
1538  ///
1539  /// When this is set to `false` you must manually grab the config object via `app.config().app.windows`
1540  /// and create it with [`WebviewWindowBuilder::from_config`](https://docs.rs/tauri/2/tauri/webview/struct.WebviewWindowBuilder.html#method.from_config).
1541  #[serde(default = "default_true")]
1542  pub create: bool,
1543  /// The window webview URL.
1544  #[serde(default)]
1545  pub url: WebviewUrl,
1546  /// The user agent for the webview
1547  #[serde(alias = "user-agent")]
1548  pub user_agent: Option<String>,
1549  /// Whether the drag and drop is enabled or not on the webview. By default it is enabled.
1550  ///
1551  /// Disabling it is required to use HTML5 drag and drop on the frontend on Windows.
1552  #[serde(default = "default_true", alias = "drag-drop-enabled")]
1553  pub drag_drop_enabled: bool,
1554  /// Whether or not the window starts centered or not.
1555  #[serde(default)]
1556  pub center: bool,
1557  /// The horizontal position of the window's top left corner
1558  pub x: Option<f64>,
1559  /// The vertical position of the window's top left corner
1560  pub y: Option<f64>,
1561  /// The window width.
1562  #[serde(default = "default_width")]
1563  pub width: f64,
1564  /// The window height.
1565  #[serde(default = "default_height")]
1566  pub height: f64,
1567  /// The min window width.
1568  #[serde(alias = "min-width")]
1569  pub min_width: Option<f64>,
1570  /// The min window height.
1571  #[serde(alias = "min-height")]
1572  pub min_height: Option<f64>,
1573  /// The max window width.
1574  #[serde(alias = "max-width")]
1575  pub max_width: Option<f64>,
1576  /// The max window height.
1577  #[serde(alias = "max-height")]
1578  pub max_height: Option<f64>,
1579  /// Whether or not to prevent the window from overflowing the workarea
1580  ///
1581  /// ## Platform-specific
1582  ///
1583  /// - **iOS / Android:** Unsupported.
1584  #[serde(alias = "prevent-overflow")]
1585  pub prevent_overflow: Option<PreventOverflowConfig>,
1586  /// Whether the window is resizable or not. When resizable is set to false, native window's maximize button is automatically disabled.
1587  #[serde(default = "default_true")]
1588  pub resizable: bool,
1589  /// Whether the window's native maximize button is enabled or not.
1590  /// If resizable is set to false, this setting is ignored.
1591  ///
1592  /// ## Platform-specific
1593  ///
1594  /// - **macOS:** Disables the "zoom" button in the window titlebar, which is also used to enter fullscreen mode.
1595  /// - **Linux / iOS / Android:** Unsupported.
1596  #[serde(default = "default_true")]
1597  pub maximizable: bool,
1598  /// Whether the window's native minimize button is enabled or not.
1599  ///
1600  /// ## Platform-specific
1601  ///
1602  /// - **Linux / iOS / Android:** Unsupported.
1603  #[serde(default = "default_true")]
1604  pub minimizable: bool,
1605  /// Whether the window's native close button is enabled or not.
1606  ///
1607  /// ## Platform-specific
1608  ///
1609  /// - **Linux:** "GTK+ will do its best to convince the window manager not to show a close button.
1610  ///   Depending on the system, this function may not have any effect when called on a window that is already visible"
1611  /// - **iOS / Android:** Unsupported.
1612  #[serde(default = "default_true")]
1613  pub closable: bool,
1614  /// The window title.
1615  #[serde(default = "default_title")]
1616  pub title: String,
1617  /// Whether the window starts as fullscreen or not.
1618  #[serde(default)]
1619  pub fullscreen: bool,
1620  /// Whether the window will be initially focused or not.
1621  #[serde(default = "default_true")]
1622  pub focus: bool,
1623  /// Whether the window is transparent or not.
1624  ///
1625  /// Note that on `macOS` this requires the `macos-private-api` feature flag, enabled under `tauri > macOSPrivateApi`.
1626  /// WARNING: Using private APIs on `macOS` prevents your application from being accepted to the `App Store`.
1627  #[serde(default)]
1628  pub transparent: bool,
1629  /// Whether the window is maximized or not.
1630  #[serde(default)]
1631  pub maximized: bool,
1632  /// Whether the window is visible or not.
1633  #[serde(default = "default_true")]
1634  pub visible: bool,
1635  /// Whether the window should have borders and bars.
1636  #[serde(default = "default_true")]
1637  pub decorations: bool,
1638  /// Whether the window should always be below other windows.
1639  #[serde(default, alias = "always-on-bottom")]
1640  pub always_on_bottom: bool,
1641  /// Whether the window should always be on top of other windows.
1642  #[serde(default, alias = "always-on-top")]
1643  pub always_on_top: bool,
1644  /// Whether the window should be visible on all workspaces or virtual desktops.
1645  ///
1646  /// ## Platform-specific
1647  ///
1648  /// - **Windows / iOS / Android:** Unsupported.
1649  #[serde(default, alias = "visible-on-all-workspaces")]
1650  pub visible_on_all_workspaces: bool,
1651  /// Prevents the window contents from being captured by other apps.
1652  #[serde(default, alias = "content-protected")]
1653  pub content_protected: bool,
1654  /// If `true`, hides the window icon from the taskbar on Windows and Linux.
1655  #[serde(default, alias = "skip-taskbar")]
1656  pub skip_taskbar: bool,
1657  /// The name of the window class created on Windows to create the window. **Windows only**.
1658  pub window_classname: Option<String>,
1659  /// The initial window theme. Defaults to the system theme. Only implemented on Windows and macOS 10.14+.
1660  pub theme: Option<crate::Theme>,
1661  /// The style of the macOS title bar.
1662  #[serde(default, alias = "title-bar-style")]
1663  pub title_bar_style: TitleBarStyle,
1664  /// The position of the window controls on macOS.
1665  ///
1666  /// Requires titleBarStyle: Overlay and decorations: true.
1667  #[serde(default, alias = "traffic-light-position")]
1668  pub traffic_light_position: Option<LogicalPosition>,
1669  /// If `true`, sets the window title to be hidden on macOS.
1670  #[serde(default, alias = "hidden-title")]
1671  pub hidden_title: bool,
1672  /// Whether clicking an inactive window also clicks through to the webview on macOS.
1673  #[serde(default, alias = "accept-first-mouse")]
1674  pub accept_first_mouse: bool,
1675  /// Defines the window [tabbing identifier] for macOS.
1676  ///
1677  /// Windows with matching tabbing identifiers will be grouped together.
1678  /// If the tabbing identifier is not set, automatic tabbing will be disabled.
1679  ///
1680  /// [tabbing identifier]: <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
1681  #[serde(default, alias = "tabbing-identifier")]
1682  pub tabbing_identifier: Option<String>,
1683  /// Defines additional browser arguments on Windows. By default wry passes `--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection`
1684  /// so if you use this method, you also need to disable these components by yourself if you want.
1685  #[serde(default, alias = "additional-browser-args")]
1686  pub additional_browser_args: Option<String>,
1687  /// Whether or not the window has shadow.
1688  ///
1689  /// ## Platform-specific
1690  ///
1691  /// - **Windows:**
1692  ///   - `false` has no effect on decorated window, shadow are always ON.
1693  ///   - `true` will make undecorated window have a 1px white border,
1694  /// and on Windows 11, it will have a rounded corners.
1695  /// - **Linux:** Unsupported.
1696  #[serde(default = "default_true")]
1697  pub shadow: bool,
1698  /// Window effects.
1699  ///
1700  /// Requires the window to be transparent.
1701  ///
1702  /// ## Platform-specific:
1703  ///
1704  /// - **Windows**: If using decorations or shadows, you may want to try this workaround <https://github.com/tauri-apps/tao/issues/72#issuecomment-975607891>
1705  /// - **Linux**: Unsupported
1706  #[serde(default, alias = "window-effects")]
1707  pub window_effects: Option<WindowEffectsConfig>,
1708  /// Whether or not the webview should be launched in incognito  mode.
1709  ///
1710  ///  ## Platform-specific:
1711  ///
1712  ///  - **Android**: Unsupported.
1713  #[serde(default)]
1714  pub incognito: bool,
1715  /// Sets the window associated with this label to be the parent of the window to be created.
1716  ///
1717  /// ## Platform-specific
1718  ///
1719  /// - **Windows**: This sets the passed parent as an owner window to the window to be created.
1720  ///   From [MSDN owned windows docs](https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows):
1721  ///     - An owned window is always above its owner in the z-order.
1722  ///     - The system automatically destroys an owned window when its owner is destroyed.
1723  ///     - An owned window is hidden when its owner is minimized.
1724  /// - **Linux**: This makes the new window transient for parent, see <https://docs.gtk.org/gtk3/method.Window.set_transient_for.html>
1725  /// - **macOS**: This adds the window as a child of parent, see <https://developer.apple.com/documentation/appkit/nswindow/1419152-addchildwindow?language=objc>
1726  pub parent: Option<String>,
1727  /// The proxy URL for the WebView for all network requests.
1728  ///
1729  /// Must be either a `http://` or a `socks5://` URL.
1730  ///
1731  /// ## Platform-specific
1732  ///
1733  /// - **macOS**: Requires the `macos-proxy` feature flag and only compiles for macOS 14+.
1734  #[serde(alias = "proxy-url")]
1735  pub proxy_url: Option<Url>,
1736  /// Whether page zooming by hotkeys is enabled
1737  ///
1738  /// ## Platform-specific:
1739  ///
1740  /// - **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.
1741  /// - **MacOS / Linux**: Injects a polyfill that zooms in and out with `ctrl/command` + `-/=`,
1742  /// 20% in each step, ranging from 20% to 1000%. Requires `webview:allow-set-webview-zoom` permission
1743  ///
1744  /// - **Android / iOS**: Unsupported.
1745  #[serde(default, alias = "zoom-hotkeys-enabled")]
1746  pub zoom_hotkeys_enabled: bool,
1747  /// Whether browser extensions can be installed for the webview process
1748  ///
1749  /// ## Platform-specific:
1750  ///
1751  /// - **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)
1752  /// - **MacOS / Linux / iOS / Android** - Unsupported.
1753  #[serde(default, alias = "browser-extensions-enabled")]
1754  pub browser_extensions_enabled: bool,
1755
1756  /// Sets whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android. Defaults to `false`.
1757  ///
1758  /// ## Note
1759  ///
1760  /// 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.
1761  ///
1762  /// ## Warning
1763  ///
1764  /// 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.
1765  #[serde(default, alias = "use-https-scheme")]
1766  pub use_https_scheme: bool,
1767  /// Enable web inspector which is usually called browser devtools. Enabled by default.
1768  ///
1769  /// This API works in **debug** builds, but requires `devtools` feature flag to enable it in **release** builds.
1770  ///
1771  /// ## Platform-specific
1772  ///
1773  /// - macOS: This will call private functions on **macOS**.
1774  /// - Android: Open `chrome://inspect/#devices` in Chrome to get the devtools window. Wry's `WebView` devtools API isn't supported on Android.
1775  /// - iOS: Open Safari > Develop > [Your Device Name] > [Your WebView] to get the devtools window.
1776  pub devtools: Option<bool>,
1777
1778  /// Set the window and webview background color.
1779  ///
1780  /// ## Platform-specific:
1781  ///
1782  /// - **Windows**: alpha channel is ignored for the window layer.
1783  /// - **Windows**: On Windows 7, alpha channel is ignored for the webview layer.
1784  /// - **Windows**: On Windows 8 and newer, if alpha channel is not `0`, it will be ignored for the webview layer.
1785  #[serde(alias = "background-color")]
1786  pub background_color: Option<Color>,
1787
1788  /// Change the default background throttling behaviour.
1789  ///
1790  /// By default, browsers use a suspend policy that will throttle timers and even unload
1791  /// the whole tab (view) to free resources after roughly 5 minutes when a view became
1792  /// minimized or hidden. This will pause all tasks until the documents visibility state
1793  /// changes back from hidden to visible by bringing the view back to the foreground.
1794  ///
1795  /// ## Platform-specific
1796  ///
1797  /// - **Linux / Windows / Android**: Unsupported. Workarounds like a pending WebLock transaction might suffice.
1798  /// - **iOS**: Supported since version 17.0+.
1799  /// - **macOS**: Supported since version 14.0+.
1800  ///
1801  /// see https://github.com/tauri-apps/tauri/issues/5250#issuecomment-2569380578
1802  #[serde(default, alias = "background-throttling")]
1803  pub background_throttling: Option<BackgroundThrottlingPolicy>,
1804  /// Whether we should disable JavaScript code execution on the webview or not.
1805  #[serde(default, alias = "javascript-disabled")]
1806  pub javascript_disabled: bool,
1807  /// on macOS and iOS there is a link preview on long pressing links, this is enabled by default.
1808  /// see https://docs.rs/objc2-web-kit/latest/objc2_web_kit/struct.WKWebView.html#method.allowsLinkPreview
1809  #[serde(default = "default_true", alias = "allow-link-preview")]
1810  pub allow_link_preview: bool,
1811  /// Allows disabling the input accessory view on iOS.
1812  ///
1813  /// The accessory view is the view that appears above the keyboard when a text input element is focused.
1814  /// It usually displays a view with "Done", "Next" buttons.
1815  #[serde(
1816    default,
1817    alias = "disable-input-accessory-view",
1818    alias = "disable_input_accessory_view"
1819  )]
1820  pub disable_input_accessory_view: bool,
1821}
1822
1823impl Default for WindowConfig {
1824  fn default() -> Self {
1825    Self {
1826      label: default_window_label(),
1827      url: WebviewUrl::default(),
1828      create: true,
1829      user_agent: None,
1830      drag_drop_enabled: true,
1831      center: false,
1832      x: None,
1833      y: None,
1834      width: default_width(),
1835      height: default_height(),
1836      min_width: None,
1837      min_height: None,
1838      max_width: None,
1839      max_height: None,
1840      prevent_overflow: None,
1841      resizable: true,
1842      maximizable: true,
1843      minimizable: true,
1844      closable: true,
1845      title: default_title(),
1846      fullscreen: false,
1847      focus: false,
1848      transparent: false,
1849      maximized: false,
1850      visible: true,
1851      decorations: true,
1852      always_on_bottom: false,
1853      always_on_top: false,
1854      visible_on_all_workspaces: false,
1855      content_protected: false,
1856      skip_taskbar: false,
1857      window_classname: None,
1858      theme: None,
1859      title_bar_style: Default::default(),
1860      traffic_light_position: None,
1861      hidden_title: false,
1862      accept_first_mouse: false,
1863      tabbing_identifier: None,
1864      additional_browser_args: None,
1865      shadow: true,
1866      window_effects: None,
1867      incognito: false,
1868      parent: None,
1869      proxy_url: None,
1870      zoom_hotkeys_enabled: false,
1871      browser_extensions_enabled: false,
1872      use_https_scheme: false,
1873      devtools: None,
1874      background_color: None,
1875      background_throttling: None,
1876      javascript_disabled: false,
1877      allow_link_preview: true,
1878      disable_input_accessory_view: false,
1879    }
1880  }
1881}
1882
1883fn default_window_label() -> String {
1884  "main".to_string()
1885}
1886
1887fn default_width() -> f64 {
1888  800f64
1889}
1890
1891fn default_height() -> f64 {
1892  600f64
1893}
1894
1895fn default_title() -> String {
1896  "Tauri App".to_string()
1897}
1898
1899/// A Content-Security-Policy directive source list.
1900/// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/Sources#sources>.
1901#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1902#[cfg_attr(feature = "schema", derive(JsonSchema))]
1903#[serde(rename_all = "camelCase", untagged)]
1904pub enum CspDirectiveSources {
1905  /// An inline list of CSP sources. Same as [`Self::List`], but concatenated with a space separator.
1906  Inline(String),
1907  /// A list of CSP sources. The collection will be concatenated with a space separator for the CSP string.
1908  List(Vec<String>),
1909}
1910
1911impl Default for CspDirectiveSources {
1912  fn default() -> Self {
1913    Self::List(Vec::new())
1914  }
1915}
1916
1917impl From<CspDirectiveSources> for Vec<String> {
1918  fn from(sources: CspDirectiveSources) -> Self {
1919    match sources {
1920      CspDirectiveSources::Inline(source) => source.split(' ').map(|s| s.to_string()).collect(),
1921      CspDirectiveSources::List(l) => l,
1922    }
1923  }
1924}
1925
1926impl CspDirectiveSources {
1927  /// Whether the given source is configured on this directive or not.
1928  pub fn contains(&self, source: &str) -> bool {
1929    match self {
1930      Self::Inline(s) => s.contains(&format!("{source} ")) || s.contains(&format!(" {source}")),
1931      Self::List(l) => l.contains(&source.into()),
1932    }
1933  }
1934
1935  /// Appends the given source to this directive.
1936  pub fn push<S: AsRef<str>>(&mut self, source: S) {
1937    match self {
1938      Self::Inline(s) => {
1939        s.push(' ');
1940        s.push_str(source.as_ref());
1941      }
1942      Self::List(l) => {
1943        l.push(source.as_ref().to_string());
1944      }
1945    }
1946  }
1947
1948  /// Extends this CSP directive source list with the given array of sources.
1949  pub fn extend(&mut self, sources: Vec<String>) {
1950    for s in sources {
1951      self.push(s);
1952    }
1953  }
1954}
1955
1956/// A Content-Security-Policy definition.
1957/// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.
1958#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1959#[cfg_attr(feature = "schema", derive(JsonSchema))]
1960#[serde(rename_all = "camelCase", untagged)]
1961pub enum Csp {
1962  /// The entire CSP policy in a single text string.
1963  Policy(String),
1964  /// An object mapping a directive with its sources values as a list of strings.
1965  DirectiveMap(HashMap<String, CspDirectiveSources>),
1966}
1967
1968impl From<HashMap<String, CspDirectiveSources>> for Csp {
1969  fn from(map: HashMap<String, CspDirectiveSources>) -> Self {
1970    Self::DirectiveMap(map)
1971  }
1972}
1973
1974impl From<Csp> for HashMap<String, CspDirectiveSources> {
1975  fn from(csp: Csp) -> Self {
1976    match csp {
1977      Csp::Policy(policy) => {
1978        let mut map = HashMap::new();
1979        for directive in policy.split(';') {
1980          let mut tokens = directive.trim().split(' ');
1981          if let Some(directive) = tokens.next() {
1982            let sources = tokens.map(|s| s.to_string()).collect::<Vec<String>>();
1983            map.insert(directive.to_string(), CspDirectiveSources::List(sources));
1984          }
1985        }
1986        map
1987      }
1988      Csp::DirectiveMap(m) => m,
1989    }
1990  }
1991}
1992
1993impl Display for Csp {
1994  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1995    match self {
1996      Self::Policy(s) => write!(f, "{s}"),
1997      Self::DirectiveMap(m) => {
1998        let len = m.len();
1999        let mut i = 0;
2000        for (directive, sources) in m {
2001          let sources: Vec<String> = sources.clone().into();
2002          write!(f, "{} {}", directive, sources.join(" "))?;
2003          i += 1;
2004          if i != len {
2005            write!(f, "; ")?;
2006          }
2007        }
2008        Ok(())
2009      }
2010    }
2011  }
2012}
2013
2014/// The possible values for the `dangerous_disable_asset_csp_modification` config option.
2015#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2016#[serde(untagged)]
2017#[cfg_attr(feature = "schema", derive(JsonSchema))]
2018pub enum DisabledCspModificationKind {
2019  /// If `true`, disables all CSP modification.
2020  /// `false` is the default value and it configures Tauri to control the CSP.
2021  Flag(bool),
2022  /// Disables the given list of CSP directives modifications.
2023  List(Vec<String>),
2024}
2025
2026impl DisabledCspModificationKind {
2027  /// Determines whether the given CSP directive can be modified or not.
2028  pub fn can_modify(&self, directive: &str) -> bool {
2029    match self {
2030      Self::Flag(f) => !f,
2031      Self::List(l) => !l.contains(&directive.into()),
2032    }
2033  }
2034}
2035
2036impl Default for DisabledCspModificationKind {
2037  fn default() -> Self {
2038    Self::Flag(false)
2039  }
2040}
2041
2042/// Protocol scope definition.
2043/// It is a list of glob patterns that restrict the API access from the webview.
2044///
2045/// Each pattern can start with a variable that resolves to a system base directory.
2046/// The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`,
2047/// `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`,
2048/// `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`,
2049/// `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.
2050#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2051#[serde(untagged)]
2052#[cfg_attr(feature = "schema", derive(JsonSchema))]
2053pub enum FsScope {
2054  /// A list of paths that are allowed by this scope.
2055  AllowedPaths(Vec<PathBuf>),
2056  /// A complete scope configuration.
2057  #[serde(rename_all = "camelCase")]
2058  Scope {
2059    /// A list of paths that are allowed by this scope.
2060    #[serde(default)]
2061    allow: Vec<PathBuf>,
2062    /// A list of paths that are not allowed by this scope.
2063    /// This gets precedence over the [`Self::Scope::allow`] list.
2064    #[serde(default)]
2065    deny: Vec<PathBuf>,
2066    /// Whether or not paths that contain components that start with a `.`
2067    /// will require that `.` appears literally in the pattern; `*`, `?`, `**`,
2068    /// or `[...]` will not match. This is useful because such files are
2069    /// conventionally considered hidden on Unix systems and it might be
2070    /// desirable to skip them when listing files.
2071    ///
2072    /// Defaults to `true` on Unix systems and `false` on Windows
2073    // dotfiles are not supposed to be exposed by default on unix
2074    #[serde(alias = "require-literal-leading-dot")]
2075    require_literal_leading_dot: Option<bool>,
2076  },
2077}
2078
2079impl Default for FsScope {
2080  fn default() -> Self {
2081    Self::AllowedPaths(Vec::new())
2082  }
2083}
2084
2085impl FsScope {
2086  /// The list of allowed paths.
2087  pub fn allowed_paths(&self) -> &Vec<PathBuf> {
2088    match self {
2089      Self::AllowedPaths(p) => p,
2090      Self::Scope { allow, .. } => allow,
2091    }
2092  }
2093
2094  /// The list of forbidden paths.
2095  pub fn forbidden_paths(&self) -> Option<&Vec<PathBuf>> {
2096    match self {
2097      Self::AllowedPaths(_) => None,
2098      Self::Scope { deny, .. } => Some(deny),
2099    }
2100  }
2101}
2102
2103/// Config for the asset custom protocol.
2104///
2105/// See more: <https://v2.tauri.app/reference/config/#assetprotocolconfig>
2106#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2107#[cfg_attr(feature = "schema", derive(JsonSchema))]
2108#[serde(rename_all = "camelCase", deny_unknown_fields)]
2109pub struct AssetProtocolConfig {
2110  /// The access scope for the asset protocol.
2111  #[serde(default)]
2112  pub scope: FsScope,
2113  /// Enables the asset protocol.
2114  #[serde(default)]
2115  pub enable: bool,
2116}
2117
2118/// definition of a header source
2119///
2120/// The header value to a header name
2121#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2122#[cfg_attr(feature = "schema", derive(JsonSchema))]
2123#[serde(rename_all = "camelCase", untagged)]
2124pub enum HeaderSource {
2125  /// string version of the header Value
2126  Inline(String),
2127  /// list version of the header value. Item are joined by "," for the real header value
2128  List(Vec<String>),
2129  /// (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
2130  Map(HashMap<String, String>),
2131}
2132
2133impl Display for HeaderSource {
2134  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2135    match self {
2136      Self::Inline(s) => write!(f, "{s}"),
2137      Self::List(l) => write!(f, "{}", l.join(", ")),
2138      Self::Map(m) => {
2139        let len = m.len();
2140        let mut i = 0;
2141        for (key, value) in m {
2142          write!(f, "{} {}", key, value)?;
2143          i += 1;
2144          if i != len {
2145            write!(f, "; ")?;
2146          }
2147        }
2148        Ok(())
2149      }
2150    }
2151  }
2152}
2153
2154/// A trait which implements on the [`Builder`] of the http create
2155///
2156/// Must add headers defined in the tauri configuration file to http responses
2157pub trait HeaderAddition {
2158  /// adds all headers defined on the config file, given the current HeaderConfig
2159  fn add_configured_headers(self, headers: Option<&HeaderConfig>) -> http::response::Builder;
2160}
2161
2162impl HeaderAddition for Builder {
2163  /// Add the headers defined in the tauri configuration file to http responses
2164  ///
2165  /// this is a utility function, which is used in the same way as the `.header(..)` of the rust http library
2166  fn add_configured_headers(mut self, headers: Option<&HeaderConfig>) -> http::response::Builder {
2167    if let Some(headers) = headers {
2168      // Add the header Access-Control-Allow-Credentials, if we find a value for it
2169      if let Some(value) = &headers.access_control_allow_credentials {
2170        self = self.header("Access-Control-Allow-Credentials", value.to_string());
2171      };
2172
2173      // Add the header Access-Control-Allow-Headers, if we find a value for it
2174      if let Some(value) = &headers.access_control_allow_headers {
2175        self = self.header("Access-Control-Allow-Headers", value.to_string());
2176      };
2177
2178      // Add the header Access-Control-Allow-Methods, if we find a value for it
2179      if let Some(value) = &headers.access_control_allow_methods {
2180        self = self.header("Access-Control-Allow-Methods", value.to_string());
2181      };
2182
2183      // Add the header Access-Control-Expose-Headers, if we find a value for it
2184      if let Some(value) = &headers.access_control_expose_headers {
2185        self = self.header("Access-Control-Expose-Headers", value.to_string());
2186      };
2187
2188      // Add the header Access-Control-Max-Age, if we find a value for it
2189      if let Some(value) = &headers.access_control_max_age {
2190        self = self.header("Access-Control-Max-Age", value.to_string());
2191      };
2192
2193      // Add the header Cross-Origin-Embedder-Policy, if we find a value for it
2194      if let Some(value) = &headers.cross_origin_embedder_policy {
2195        self = self.header("Cross-Origin-Embedder-Policy", value.to_string());
2196      };
2197
2198      // Add the header Cross-Origin-Opener-Policy, if we find a value for it
2199      if let Some(value) = &headers.cross_origin_opener_policy {
2200        self = self.header("Cross-Origin-Opener-Policy", value.to_string());
2201      };
2202
2203      // Add the header Cross-Origin-Resource-Policy, if we find a value for it
2204      if let Some(value) = &headers.cross_origin_resource_policy {
2205        self = self.header("Cross-Origin-Resource-Policy", value.to_string());
2206      };
2207
2208      // Add the header Permission-Policy, if we find a value for it
2209      if let Some(value) = &headers.permissions_policy {
2210        self = self.header("Permission-Policy", value.to_string());
2211      };
2212
2213      // Add the header Timing-Allow-Origin, if we find a value for it
2214      if let Some(value) = &headers.timing_allow_origin {
2215        self = self.header("Timing-Allow-Origin", value.to_string());
2216      };
2217
2218      // Add the header X-Content-Type-Options, if we find a value for it
2219      if let Some(value) = &headers.x_content_type_options {
2220        self = self.header("X-Content-Type-Options", value.to_string());
2221      };
2222
2223      // Add the header Tauri-Custom-Header, if we find a value for it
2224      if let Some(value) = &headers.tauri_custom_header {
2225        // Keep in mind to correctly set the Access-Control-Expose-Headers
2226        self = self.header("Tauri-Custom-Header", value.to_string());
2227      };
2228    }
2229    self
2230  }
2231}
2232
2233/// A struct, where the keys are some specific http header names.
2234///
2235/// If the values to those keys are defined, then they will be send as part of a response message.
2236/// This does not include error messages and ipc messages
2237///
2238/// ## Example configuration
2239/// ```javascript
2240/// {
2241///  //..
2242///   app:{
2243///     //..
2244///     security: {
2245///       headers: {
2246///         "Cross-Origin-Opener-Policy": "same-origin",
2247///         "Cross-Origin-Embedder-Policy": "require-corp",
2248///         "Timing-Allow-Origin": [
2249///           "https://developer.mozilla.org",
2250///           "https://example.com",
2251///         ],
2252///         "Access-Control-Expose-Headers": "Tauri-Custom-Header",
2253///         "Tauri-Custom-Header": {
2254///           "key1": "'value1' 'value2'",
2255///           "key2": "'value3'"
2256///         }
2257///       },
2258///       csp: "default-src 'self'; connect-src ipc: http://ipc.localhost",
2259///     }
2260///     //..
2261///   }
2262///  //..
2263/// }
2264/// ```
2265/// 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).
2266/// 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.
2267/// The Content-Security-Policy header is defined separately, because it is also handled separately.
2268///
2269/// For the helloworld example, this config translates into those response headers:
2270/// ```http
2271/// access-control-allow-origin:  http://tauri.localhost
2272/// access-control-expose-headers: Tauri-Custom-Header
2273/// content-security-policy: default-src 'self'; connect-src ipc: http://ipc.localhost; script-src 'self' 'sha256-Wjjrs6qinmnr+tOry8x8PPwI77eGpUFR3EEGZktjJNs='
2274/// content-type: text/html
2275/// cross-origin-embedder-policy: require-corp
2276/// cross-origin-opener-policy: same-origin
2277/// tauri-custom-header: key1 'value1' 'value2'; key2 'value3'
2278/// timing-allow-origin: https://developer.mozilla.org, https://example.com
2279/// ```
2280/// Since the resulting header values are always 'string-like'. So depending on the what data type the HeaderSource is, they need to be converted.
2281///  - `String`(JS/Rust): stay the same for the resulting header value
2282///  - `Array`(JS)/`Vec\<String\>`(Rust): Item are joined by ", " for the resulting header value
2283///  - `Object`(JS)/ `Hashmap\<String,String\>`(Rust): Items are composed from: key + space + value. Item are then joined by "; " for the resulting header value
2284#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2285#[cfg_attr(feature = "schema", derive(JsonSchema))]
2286#[serde(deny_unknown_fields)]
2287pub struct HeaderConfig {
2288  /// The Access-Control-Allow-Credentials response header tells browsers whether the
2289  /// server allows cross-origin HTTP requests to include credentials.
2290  ///
2291  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials>
2292  #[serde(rename = "Access-Control-Allow-Credentials")]
2293  pub access_control_allow_credentials: Option<HeaderSource>,
2294  /// The Access-Control-Allow-Headers response header is used in response
2295  /// to a preflight request which includes the Access-Control-Request-Headers
2296  /// to indicate which HTTP headers can be used during the actual request.
2297  ///
2298  /// This header is required if the request has an Access-Control-Request-Headers header.
2299  ///
2300  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers>
2301  #[serde(rename = "Access-Control-Allow-Headers")]
2302  pub access_control_allow_headers: Option<HeaderSource>,
2303  /// The Access-Control-Allow-Methods response header specifies one or more methods
2304  /// allowed when accessing a resource in response to a preflight request.
2305  ///
2306  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods>
2307  #[serde(rename = "Access-Control-Allow-Methods")]
2308  pub access_control_allow_methods: Option<HeaderSource>,
2309  /// The Access-Control-Expose-Headers response header allows a server to indicate
2310  /// which response headers should be made available to scripts running in the browser,
2311  /// in response to a cross-origin request.
2312  ///
2313  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers>
2314  #[serde(rename = "Access-Control-Expose-Headers")]
2315  pub access_control_expose_headers: Option<HeaderSource>,
2316  /// The Access-Control-Max-Age response header indicates how long the results of a
2317  /// preflight request (that is the information contained in the
2318  /// Access-Control-Allow-Methods and Access-Control-Allow-Headers headers) can
2319  /// be cached.
2320  ///
2321  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age>
2322  #[serde(rename = "Access-Control-Max-Age")]
2323  pub access_control_max_age: Option<HeaderSource>,
2324  /// The HTTP Cross-Origin-Embedder-Policy (COEP) response header configures embedding
2325  /// cross-origin resources into the document.
2326  ///
2327  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy>
2328  #[serde(rename = "Cross-Origin-Embedder-Policy")]
2329  pub cross_origin_embedder_policy: Option<HeaderSource>,
2330  /// The HTTP Cross-Origin-Opener-Policy (COOP) response header allows you to ensure a
2331  /// top-level document does not share a browsing context group with cross-origin documents.
2332  /// COOP will process-isolate your document and potential attackers can't access your global
2333  /// object if they were to open it in a popup, preventing a set of cross-origin attacks dubbed XS-Leaks.
2334  ///
2335  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy>
2336  #[serde(rename = "Cross-Origin-Opener-Policy")]
2337  pub cross_origin_opener_policy: Option<HeaderSource>,
2338  /// The HTTP Cross-Origin-Resource-Policy response header conveys a desire that the
2339  /// browser blocks no-cors cross-origin/cross-site requests to the given resource.
2340  ///
2341  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy>
2342  #[serde(rename = "Cross-Origin-Resource-Policy")]
2343  pub cross_origin_resource_policy: Option<HeaderSource>,
2344  /// The HTTP Permissions-Policy header provides a mechanism to allow and deny the
2345  /// use of browser features in a document or within any \<iframe\> elements in the document.
2346  ///
2347  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy>
2348  #[serde(rename = "Permissions-Policy")]
2349  pub permissions_policy: Option<HeaderSource>,
2350  /// The Timing-Allow-Origin response header specifies origins that are allowed to see values
2351  /// of attributes retrieved via features of the Resource Timing API, which would otherwise be
2352  /// reported as zero due to cross-origin restrictions.
2353  ///
2354  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Timing-Allow-Origin>
2355  #[serde(rename = "Timing-Allow-Origin")]
2356  pub timing_allow_origin: Option<HeaderSource>,
2357  /// The X-Content-Type-Options response HTTP header is a marker used by the server to indicate
2358  /// that the MIME types advertised in the Content-Type headers should be followed and not be
2359  /// changed. The header allows you to avoid MIME type sniffing by saying that the MIME types
2360  /// are deliberately configured.
2361  ///
2362  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options>
2363  #[serde(rename = "X-Content-Type-Options")]
2364  pub x_content_type_options: Option<HeaderSource>,
2365  /// A custom header field Tauri-Custom-Header, don't use it.
2366  /// Remember to set Access-Control-Expose-Headers accordingly
2367  ///
2368  /// **NOT INTENDED FOR PRODUCTION USE**
2369  #[serde(rename = "Tauri-Custom-Header")]
2370  pub tauri_custom_header: Option<HeaderSource>,
2371}
2372
2373impl HeaderConfig {
2374  /// creates a new header config
2375  pub fn new() -> Self {
2376    HeaderConfig {
2377      access_control_allow_credentials: None,
2378      access_control_allow_methods: None,
2379      access_control_allow_headers: None,
2380      access_control_expose_headers: None,
2381      access_control_max_age: None,
2382      cross_origin_embedder_policy: None,
2383      cross_origin_opener_policy: None,
2384      cross_origin_resource_policy: None,
2385      permissions_policy: None,
2386      timing_allow_origin: None,
2387      x_content_type_options: None,
2388      tauri_custom_header: None,
2389    }
2390  }
2391}
2392
2393/// Security configuration.
2394///
2395/// See more: <https://v2.tauri.app/reference/config/#securityconfig>
2396#[skip_serializing_none]
2397#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
2398#[cfg_attr(feature = "schema", derive(JsonSchema))]
2399#[serde(rename_all = "camelCase", deny_unknown_fields)]
2400pub struct SecurityConfig {
2401  /// The Content Security Policy that will be injected on all HTML files on the built application.
2402  /// If [`dev_csp`](#SecurityConfig.devCsp) is not specified, this value is also injected on dev.
2403  ///
2404  /// This is a really important part of the configuration since it helps you ensure your WebView is secured.
2405  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.
2406  pub csp: Option<Csp>,
2407  /// The Content Security Policy that will be injected on all HTML files on development.
2408  ///
2409  /// This is a really important part of the configuration since it helps you ensure your WebView is secured.
2410  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.
2411  #[serde(alias = "dev-csp")]
2412  pub dev_csp: Option<Csp>,
2413  /// Freeze the `Object.prototype` when using the custom protocol.
2414  #[serde(default, alias = "freeze-prototype")]
2415  pub freeze_prototype: bool,
2416  /// Disables the Tauri-injected CSP sources.
2417  ///
2418  /// At compile time, Tauri parses all the frontend assets and changes the Content-Security-Policy
2419  /// to only allow loading of your own scripts and styles by injecting nonce and hash sources.
2420  /// This stricts your CSP, which may introduce issues when using along with other flexing sources.
2421  ///
2422  /// This configuration option allows both a boolean and a list of strings as value.
2423  /// A boolean instructs Tauri to disable the injection for all CSP injections,
2424  /// and a list of strings indicates the CSP directives that Tauri cannot inject.
2425  ///
2426  /// **WARNING:** Only disable this if you know what you are doing and have properly configured the CSP.
2427  /// Your application might be vulnerable to XSS attacks without this Tauri protection.
2428  #[serde(default, alias = "dangerous-disable-asset-csp-modification")]
2429  pub dangerous_disable_asset_csp_modification: DisabledCspModificationKind,
2430  /// Custom protocol config.
2431  #[serde(default, alias = "asset-protocol")]
2432  pub asset_protocol: AssetProtocolConfig,
2433  /// The pattern to use.
2434  #[serde(default)]
2435  pub pattern: PatternKind,
2436  /// List of capabilities that are enabled on the application.
2437  ///
2438  /// If the list is empty, all capabilities are included.
2439  #[serde(default)]
2440  pub capabilities: Vec<CapabilityEntry>,
2441  /// The headers, which are added to every http response from tauri to the web view
2442  /// This doesn't include IPC Messages and error responses
2443  #[serde(default)]
2444  pub headers: Option<HeaderConfig>,
2445}
2446
2447/// A capability entry which can be either an inlined capability or a reference to a capability defined on its own file.
2448#[derive(Debug, Clone, PartialEq, Serialize)]
2449#[cfg_attr(feature = "schema", derive(JsonSchema))]
2450#[serde(untagged)]
2451pub enum CapabilityEntry {
2452  /// An inlined capability.
2453  Inlined(Capability),
2454  /// Reference to a capability identifier.
2455  Reference(String),
2456}
2457
2458impl<'de> Deserialize<'de> for CapabilityEntry {
2459  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2460  where
2461    D: Deserializer<'de>,
2462  {
2463    UntaggedEnumVisitor::new()
2464      .string(|string| Ok(Self::Reference(string.to_owned())))
2465      .map(|map| map.deserialize::<Capability>().map(Self::Inlined))
2466      .deserialize(deserializer)
2467  }
2468}
2469
2470/// The application pattern.
2471#[skip_serializing_none]
2472#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
2473#[serde(rename_all = "lowercase", tag = "use", content = "options")]
2474#[cfg_attr(feature = "schema", derive(JsonSchema))]
2475pub enum PatternKind {
2476  /// Brownfield pattern.
2477  Brownfield,
2478  /// Isolation pattern. Recommended for security purposes.
2479  Isolation {
2480    /// The dir containing the index.html file that contains the secure isolation application.
2481    dir: PathBuf,
2482  },
2483}
2484
2485impl Default for PatternKind {
2486  fn default() -> Self {
2487    Self::Brownfield
2488  }
2489}
2490
2491/// The App configuration object.
2492///
2493/// See more: <https://v2.tauri.app/reference/config/#appconfig>
2494#[skip_serializing_none]
2495#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
2496#[cfg_attr(feature = "schema", derive(JsonSchema))]
2497#[serde(rename_all = "camelCase", deny_unknown_fields)]
2498pub struct AppConfig {
2499  /// The app windows configuration.
2500  #[serde(default)]
2501  pub windows: Vec<WindowConfig>,
2502  /// Security configuration.
2503  #[serde(default)]
2504  pub security: SecurityConfig,
2505  /// Configuration for app tray icon.
2506  #[serde(alias = "tray-icon")]
2507  pub tray_icon: Option<TrayIconConfig>,
2508  /// MacOS private API configuration. Enables the transparent background API and sets the `fullScreenEnabled` preference to `true`.
2509  #[serde(rename = "macOSPrivateApi", alias = "macos-private-api", default)]
2510  pub macos_private_api: bool,
2511  /// Whether we should inject the Tauri API on `window.__TAURI__` or not.
2512  #[serde(default, alias = "with-global-tauri")]
2513  pub with_global_tauri: bool,
2514  /// If set to true "identifier" will be set as GTK app ID (on systems that use GTK).
2515  #[serde(rename = "enableGTKAppId", alias = "enable-gtk-app-id", default)]
2516  pub enable_gtk_app_id: bool,
2517}
2518
2519impl AppConfig {
2520  /// Returns all Cargo features.
2521  pub fn all_features() -> Vec<&'static str> {
2522    vec![
2523      "tray-icon",
2524      "macos-private-api",
2525      "protocol-asset",
2526      "isolation",
2527    ]
2528  }
2529
2530  /// Returns the enabled Cargo features.
2531  pub fn features(&self) -> Vec<&str> {
2532    let mut features = Vec::new();
2533    if self.tray_icon.is_some() {
2534      features.push("tray-icon");
2535    }
2536    if self.macos_private_api {
2537      features.push("macos-private-api");
2538    }
2539    if self.security.asset_protocol.enable {
2540      features.push("protocol-asset");
2541    }
2542
2543    if let PatternKind::Isolation { .. } = self.security.pattern {
2544      features.push("isolation");
2545    }
2546
2547    features.sort_unstable();
2548    features
2549  }
2550}
2551
2552/// Configuration for application tray icon.
2553///
2554/// See more: <https://v2.tauri.app/reference/config/#trayiconconfig>
2555#[skip_serializing_none]
2556#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2557#[cfg_attr(feature = "schema", derive(JsonSchema))]
2558#[serde(rename_all = "camelCase", deny_unknown_fields)]
2559pub struct TrayIconConfig {
2560  /// Set an id for this tray icon so you can reference it later, defaults to `main`.
2561  pub id: Option<String>,
2562  /// Path to the default icon to use for the tray icon.
2563  ///
2564  /// Note: this stores the image in raw pixels to the final binary,
2565  /// so keep the icon size (width and height) small
2566  /// or else it's going to bloat your final executable
2567  #[serde(alias = "icon-path")]
2568  pub icon_path: PathBuf,
2569  /// 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.
2570  #[serde(default, alias = "icon-as-template")]
2571  pub icon_as_template: bool,
2572  /// A Boolean value that determines whether the menu should appear when the tray icon receives a left click.
2573  ///
2574  /// ## Platform-specific:
2575  ///
2576  /// - **Linux**: Unsupported.
2577  #[serde(default = "default_true", alias = "menu-on-left-click")]
2578  #[deprecated(since = "2.2.0", note = "Use `show_menu_on_left_click` instead.")]
2579  pub menu_on_left_click: bool,
2580  /// A Boolean value that determines whether the menu should appear when the tray icon receives a left click.
2581  ///
2582  /// ## Platform-specific:
2583  ///
2584  /// - **Linux**: Unsupported.
2585  #[serde(default = "default_true", alias = "show-menu-on-left-click")]
2586  pub show_menu_on_left_click: bool,
2587  /// Title for MacOS tray
2588  pub title: Option<String>,
2589  /// Tray icon tooltip on Windows and macOS
2590  pub tooltip: Option<String>,
2591}
2592
2593/// General configuration for the iOS target.
2594#[skip_serializing_none]
2595#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2596#[cfg_attr(feature = "schema", derive(JsonSchema))]
2597#[serde(rename_all = "camelCase", deny_unknown_fields)]
2598pub struct IosConfig {
2599  /// A custom [XcodeGen] project.yml template to use.
2600  ///
2601  /// [XcodeGen]: <https://github.com/yonaskolb/XcodeGen>
2602  pub template: Option<PathBuf>,
2603  /// A list of strings indicating any iOS frameworks that need to be bundled with the application.
2604  ///
2605  /// Note that you need to recreate the iOS project for the changes to be applied.
2606  pub frameworks: Option<Vec<String>>,
2607  /// The development team. This value is required for iOS development because code signing is enforced.
2608  /// The `APPLE_DEVELOPMENT_TEAM` environment variable can be set to overwrite it.
2609  #[serde(alias = "development-team")]
2610  pub development_team: Option<String>,
2611  /// The version of the build that identifies an iteration of the bundle.
2612  ///
2613  /// Translates to the bundle's CFBundleVersion property.
2614  #[serde(alias = "bundle-version")]
2615  pub bundle_version: Option<String>,
2616  /// A version string indicating the minimum iOS version that the bundled application supports. Defaults to `13.0`.
2617  ///
2618  /// Maps to the IPHONEOS_DEPLOYMENT_TARGET value.
2619  #[serde(
2620    alias = "minimum-system-version",
2621    default = "ios_minimum_system_version"
2622  )]
2623  pub minimum_system_version: String,
2624}
2625
2626impl Default for IosConfig {
2627  fn default() -> Self {
2628    Self {
2629      template: None,
2630      frameworks: None,
2631      development_team: None,
2632      bundle_version: None,
2633      minimum_system_version: ios_minimum_system_version(),
2634    }
2635  }
2636}
2637
2638/// General configuration for the Android target.
2639#[skip_serializing_none]
2640#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2641#[cfg_attr(feature = "schema", derive(JsonSchema))]
2642#[serde(rename_all = "camelCase", deny_unknown_fields)]
2643pub struct AndroidConfig {
2644  /// The minimum API level required for the application to run.
2645  /// The Android system will prevent the user from installing the application if the system's API level is lower than the value specified.
2646  #[serde(alias = "min-sdk-version", default = "default_min_sdk_version")]
2647  pub min_sdk_version: u32,
2648
2649  /// The version code of the application.
2650  /// It is limited to 2,100,000,000 as per Google Play Store requirements.
2651  ///
2652  /// By default we use your configured version and perform the following math:
2653  /// versionCode = version.major * 1000000 + version.minor * 1000 + version.patch
2654  #[serde(alias = "version-code")]
2655  #[cfg_attr(feature = "schema", validate(range(min = 1, max = 2_100_000_000)))]
2656  pub version_code: Option<u32>,
2657}
2658
2659impl Default for AndroidConfig {
2660  fn default() -> Self {
2661    Self {
2662      min_sdk_version: default_min_sdk_version(),
2663      version_code: None,
2664    }
2665  }
2666}
2667
2668fn default_min_sdk_version() -> u32 {
2669  24
2670}
2671
2672/// Defines the URL or assets to embed in the application.
2673#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2674#[cfg_attr(feature = "schema", derive(JsonSchema))]
2675#[serde(untagged, deny_unknown_fields)]
2676#[non_exhaustive]
2677pub enum FrontendDist {
2678  /// An external URL that should be used as the default application URL.
2679  Url(Url),
2680  /// Path to a directory containing the frontend dist assets.
2681  Directory(PathBuf),
2682  /// An array of files to embed on the app.
2683  Files(Vec<PathBuf>),
2684}
2685
2686impl std::fmt::Display for FrontendDist {
2687  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2688    match self {
2689      Self::Url(url) => write!(f, "{url}"),
2690      Self::Directory(p) => write!(f, "{}", p.display()),
2691      Self::Files(files) => write!(f, "{}", serde_json::to_string(files).unwrap()),
2692    }
2693  }
2694}
2695
2696/// Describes the shell command to run before `tauri dev`.
2697#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2698#[cfg_attr(feature = "schema", derive(JsonSchema))]
2699#[serde(rename_all = "camelCase", untagged)]
2700pub enum BeforeDevCommand {
2701  /// Run the given script with the default options.
2702  Script(String),
2703  /// Run the given script with custom options.
2704  ScriptWithOptions {
2705    /// The script to execute.
2706    script: String,
2707    /// The current working directory.
2708    cwd: Option<String>,
2709    /// Whether `tauri dev` should wait for the command to finish or not. Defaults to `false`.
2710    #[serde(default)]
2711    wait: bool,
2712  },
2713}
2714
2715/// Describes a shell command to be executed when a CLI hook is triggered.
2716#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2717#[cfg_attr(feature = "schema", derive(JsonSchema))]
2718#[serde(rename_all = "camelCase", untagged)]
2719pub enum HookCommand {
2720  /// Run the given script with the default options.
2721  Script(String),
2722  /// Run the given script with custom options.
2723  ScriptWithOptions {
2724    /// The script to execute.
2725    script: String,
2726    /// The current working directory.
2727    cwd: Option<String>,
2728  },
2729}
2730
2731/// The Build configuration object.
2732///
2733/// See more: <https://v2.tauri.app/reference/config/#buildconfig>
2734#[skip_serializing_none]
2735#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Default)]
2736#[cfg_attr(feature = "schema", derive(JsonSchema))]
2737#[serde(rename_all = "camelCase", deny_unknown_fields)]
2738pub struct BuildConfig {
2739  /// The binary used to build and run the application.
2740  pub runner: Option<String>,
2741  /// The URL to load in development.
2742  ///
2743  /// This is usually an URL to a dev server, which serves your application assets with hot-reload and HMR.
2744  /// Most modern JavaScript bundlers like [Vite](https://vite.dev/guide/) provides a way to start a dev server by default.
2745  ///
2746  /// If you don't have a dev server or don't want to use one, ignore this option and use [`frontendDist`](BuildConfig::frontend_dist)
2747  /// and point to a web assets directory, and Tauri CLI will run its built-in dev server and provide a simple hot-reload experience.
2748  #[serde(alias = "dev-url")]
2749  pub dev_url: Option<Url>,
2750  /// The path to the application assets (usually the `dist` folder of your javascript bundler)
2751  /// or a URL that could be either a custom protocol registered in the tauri app (for example: `myprotocol://`)
2752  /// or a remote URL (for example: `https://site.com/app`).
2753  ///
2754  /// When a path relative to the configuration file is provided,
2755  /// it is read recursively and all files are embedded in the application binary.
2756  /// Tauri then looks for an `index.html` and serves it as the default entry point for your application.
2757  ///
2758  /// You can also provide a list of paths to be embedded, which allows granular control over what files are added to the binary.
2759  /// In this case, all files are added to the root and you must reference it that way in your HTML files.
2760  ///
2761  /// When a URL is provided, the application won't have bundled assets
2762  /// and the application will load that URL by default.
2763  #[serde(alias = "frontend-dist")]
2764  pub frontend_dist: Option<FrontendDist>,
2765  /// A shell command to run before `tauri dev` kicks in.
2766  ///
2767  /// The TAURI_ENV_PLATFORM, TAURI_ENV_ARCH, TAURI_ENV_FAMILY, TAURI_ENV_PLATFORM_VERSION, TAURI_ENV_PLATFORM_TYPE and TAURI_ENV_DEBUG environment variables are set if you perform conditional compilation.
2768  #[serde(alias = "before-dev-command")]
2769  pub before_dev_command: Option<BeforeDevCommand>,
2770  /// A shell command to run before `tauri build` kicks in.
2771  ///
2772  /// The TAURI_ENV_PLATFORM, TAURI_ENV_ARCH, TAURI_ENV_FAMILY, TAURI_ENV_PLATFORM_VERSION, TAURI_ENV_PLATFORM_TYPE and TAURI_ENV_DEBUG environment variables are set if you perform conditional compilation.
2773  #[serde(alias = "before-build-command")]
2774  pub before_build_command: Option<HookCommand>,
2775  /// A shell command to run before the bundling phase in `tauri build` kicks in.
2776  ///
2777  /// The TAURI_ENV_PLATFORM, TAURI_ENV_ARCH, TAURI_ENV_FAMILY, TAURI_ENV_PLATFORM_VERSION, TAURI_ENV_PLATFORM_TYPE and TAURI_ENV_DEBUG environment variables are set if you perform conditional compilation.
2778  #[serde(alias = "before-bundle-command")]
2779  pub before_bundle_command: Option<HookCommand>,
2780  /// Features passed to `cargo` commands.
2781  pub features: Option<Vec<String>>,
2782  /// Try to remove unused commands registered from plugins base on the ACL list during `tauri build`,
2783  /// the way it works is that tauri-cli will read this and set the environment variables for the build script and macros,
2784  /// and they'll try to get all the allowed commands and remove the rest
2785  ///
2786  /// Note:
2787  ///   - This won't be accounting for dynamically added ACLs so make sure to check it when using this
2788  ///   - This feature requires tauri-plugin 2.1 and tauri 2.4
2789  #[serde(alias = "remove-unused-commands", default)]
2790  pub remove_unused_commands: bool,
2791}
2792
2793#[derive(Debug, PartialEq, Eq)]
2794struct PackageVersion(String);
2795
2796impl<'d> serde::Deserialize<'d> for PackageVersion {
2797  fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {
2798    struct PackageVersionVisitor;
2799
2800    impl Visitor<'_> for PackageVersionVisitor {
2801      type Value = PackageVersion;
2802
2803      fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2804        write!(
2805          formatter,
2806          "a semver string or a path to a package.json file"
2807        )
2808      }
2809
2810      fn visit_str<E: DeError>(self, value: &str) -> Result<PackageVersion, E> {
2811        let path = PathBuf::from(value);
2812        if path.exists() {
2813          let json_str = read_to_string(&path)
2814            .map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?;
2815          let package_json: serde_json::Value = serde_json::from_str(&json_str)
2816            .map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?;
2817          if let Some(obj) = package_json.as_object() {
2818            let version = obj
2819              .get("version")
2820              .ok_or_else(|| DeError::custom("JSON must contain a `version` field"))?
2821              .as_str()
2822              .ok_or_else(|| {
2823                DeError::custom(format!("`{} > version` must be a string", path.display()))
2824              })?;
2825            Ok(PackageVersion(
2826              Version::from_str(version)
2827                .map_err(|_| DeError::custom("`package > version` must be a semver string"))?
2828                .to_string(),
2829            ))
2830          } else {
2831            Err(DeError::custom(
2832              "`package > version` value is not a path to a JSON object",
2833            ))
2834          }
2835        } else {
2836          Ok(PackageVersion(
2837            Version::from_str(value)
2838              .map_err(|_| DeError::custom("`package > version` must be a semver string"))?
2839              .to_string(),
2840          ))
2841        }
2842      }
2843    }
2844
2845    deserializer.deserialize_string(PackageVersionVisitor {})
2846  }
2847}
2848
2849fn version_deserializer<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
2850where
2851  D: Deserializer<'de>,
2852{
2853  Option::<PackageVersion>::deserialize(deserializer).map(|v| v.map(|v| v.0))
2854}
2855
2856/// The Tauri configuration object.
2857/// It is read from a file where you can define your frontend assets,
2858/// configure the bundler and define a tray icon.
2859///
2860/// The configuration file is generated by the
2861/// [`tauri init`](https://v2.tauri.app/reference/cli/#init) command that lives in
2862/// your Tauri application source directory (src-tauri).
2863///
2864/// Once generated, you may modify it at will to customize your Tauri application.
2865///
2866/// ## File Formats
2867///
2868/// By default, the configuration is defined as a JSON file named `tauri.conf.json`.
2869///
2870/// Tauri also supports JSON5 and TOML files via the `config-json5` and `config-toml` Cargo features, respectively.
2871/// The JSON5 file name must be either `tauri.conf.json` or `tauri.conf.json5`.
2872/// The TOML file name is `Tauri.toml`.
2873///
2874/// ## Platform-Specific Configuration
2875///
2876/// In addition to the default configuration file, Tauri can
2877/// read a platform-specific configuration from `tauri.linux.conf.json`,
2878/// `tauri.windows.conf.json`, `tauri.macos.conf.json`, `tauri.android.conf.json` and `tauri.ios.conf.json`
2879/// (or `Tauri.linux.toml`, `Tauri.windows.toml`, `Tauri.macos.toml`, `Tauri.android.toml` and `Tauri.ios.toml` if the `Tauri.toml` format is used),
2880/// which gets merged with the main configuration object.
2881///
2882/// ## Configuration Structure
2883///
2884/// The configuration is composed of the following objects:
2885///
2886/// - [`app`](#appconfig): The Tauri configuration
2887/// - [`build`](#buildconfig): The build configuration
2888/// - [`bundle`](#bundleconfig): The bundle configurations
2889/// - [`plugins`](#pluginconfig): The plugins configuration
2890///
2891/// Example tauri.config.json file:
2892///
2893/// ```json
2894/// {
2895///   "productName": "tauri-app",
2896///   "version": "0.1.0",
2897///   "build": {
2898///     "beforeBuildCommand": "",
2899///     "beforeDevCommand": "",
2900///     "devUrl": "http://localhost:3000",
2901///     "frontendDist": "../dist"
2902///   },
2903///   "app": {
2904///     "security": {
2905///       "csp": null
2906///     },
2907///     "windows": [
2908///       {
2909///         "fullscreen": false,
2910///         "height": 600,
2911///         "resizable": true,
2912///         "title": "Tauri App",
2913///         "width": 800
2914///       }
2915///     ]
2916///   },
2917///   "bundle": {},
2918///   "plugins": {}
2919/// }
2920/// ```
2921#[skip_serializing_none]
2922#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
2923#[cfg_attr(feature = "schema", derive(JsonSchema))]
2924#[serde(rename_all = "camelCase", deny_unknown_fields)]
2925pub struct Config {
2926  /// The JSON schema for the Tauri config.
2927  #[serde(rename = "$schema")]
2928  pub schema: Option<String>,
2929  /// App name.
2930  #[serde(alias = "product-name")]
2931  #[cfg_attr(feature = "schema", validate(regex(pattern = "^[^/\\:*?\"<>|]+$")))]
2932  pub product_name: Option<String>,
2933  /// App main binary filename. Defaults to the name of your cargo crate.
2934  #[serde(alias = "main-binary-name")]
2935  pub main_binary_name: Option<String>,
2936  /// App version. It is a semver version number or a path to a `package.json` file containing the `version` field.
2937  ///
2938  /// If removed the version number from `Cargo.toml` is used.
2939  /// It's recommended to manage the app versioning in the Tauri config.
2940  ///
2941  /// ## Platform-specific
2942  ///
2943  /// - **macOS**: Translates to the bundle's CFBundleShortVersionString property and is used as the default CFBundleVersion.
2944  ///    You can set an specific bundle version using [`bundle > macOS > bundleVersion`](MacConfig::bundle_version).
2945  /// - **iOS**: Translates to the bundle's CFBundleShortVersionString property and is used as the default CFBundleVersion.
2946  ///    You can set an specific bundle version using [`bundle > iOS > bundleVersion`](IosConfig::bundle_version).
2947  ///    The `tauri ios build` CLI command has a `--build-number <number>` option that lets you append a build number to the app version.
2948  /// - **Android**: By default version 1.0 is used. You can set a version code using [`bundle > android > versionCode`](AndroidConfig::version_code).
2949  ///
2950  /// By default version 1.0 is used on Android.
2951  #[serde(deserialize_with = "version_deserializer", default)]
2952  pub version: Option<String>,
2953  /// The application identifier in reverse domain name notation (e.g. `com.tauri.example`).
2954  /// This string must be unique across applications since it is used in system configurations like
2955  /// the bundle ID and path to the webview data directory.
2956  /// This string must contain only alphanumeric characters (A-Z, a-z, and 0-9), hyphens (-),
2957  /// and periods (.).
2958  pub identifier: String,
2959  /// The App configuration.
2960  #[serde(default)]
2961  pub app: AppConfig,
2962  /// The build configuration.
2963  #[serde(default)]
2964  pub build: BuildConfig,
2965  /// The bundler configuration.
2966  #[serde(default)]
2967  pub bundle: BundleConfig,
2968  /// The plugins config.
2969  #[serde(default)]
2970  pub plugins: PluginConfig,
2971}
2972
2973/// The plugin configs holds a HashMap mapping a plugin name to its configuration object.
2974///
2975/// See more: <https://v2.tauri.app/reference/config/#pluginconfig>
2976#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
2977#[cfg_attr(feature = "schema", derive(JsonSchema))]
2978pub struct PluginConfig(pub HashMap<String, JsonValue>);
2979
2980/// Implement `ToTokens` for all config structs, allowing a literal `Config` to be built.
2981///
2982/// This allows for a build script to output the values in a `Config` to a `TokenStream`, which can
2983/// then be consumed by another crate. Useful for passing a config to both the build script and the
2984/// application using tauri while only parsing it once (in the build script).
2985#[cfg(feature = "build")]
2986mod build {
2987  use super::*;
2988  use crate::{literal_struct, tokens::*};
2989  use proc_macro2::TokenStream;
2990  use quote::{quote, ToTokens, TokenStreamExt};
2991  use std::convert::identity;
2992
2993  impl ToTokens for WebviewUrl {
2994    fn to_tokens(&self, tokens: &mut TokenStream) {
2995      let prefix = quote! { ::tauri::utils::config::WebviewUrl };
2996
2997      tokens.append_all(match self {
2998        Self::App(path) => {
2999          let path = path_buf_lit(path);
3000          quote! { #prefix::App(#path) }
3001        }
3002        Self::External(url) => {
3003          let url = url_lit(url);
3004          quote! { #prefix::External(#url) }
3005        }
3006        Self::CustomProtocol(url) => {
3007          let url = url_lit(url);
3008          quote! { #prefix::CustomProtocol(#url) }
3009        }
3010      })
3011    }
3012  }
3013
3014  impl ToTokens for BackgroundThrottlingPolicy {
3015    fn to_tokens(&self, tokens: &mut TokenStream) {
3016      let prefix = quote! { ::tauri::utils::config::BackgroundThrottlingPolicy };
3017      tokens.append_all(match self {
3018        Self::Disabled => quote! { #prefix::Disabled },
3019        Self::Throttle => quote! { #prefix::Throttle },
3020        Self::Suspend => quote! { #prefix::Suspend },
3021      })
3022    }
3023  }
3024
3025  impl ToTokens for crate::Theme {
3026    fn to_tokens(&self, tokens: &mut TokenStream) {
3027      let prefix = quote! { ::tauri::utils::Theme };
3028
3029      tokens.append_all(match self {
3030        Self::Light => quote! { #prefix::Light },
3031        Self::Dark => quote! { #prefix::Dark },
3032      })
3033    }
3034  }
3035
3036  impl ToTokens for Color {
3037    fn to_tokens(&self, tokens: &mut TokenStream) {
3038      let Color(r, g, b, a) = self;
3039      tokens.append_all(quote! {::tauri::utils::config::Color(#r,#g,#b,#a)});
3040    }
3041  }
3042  impl ToTokens for WindowEffectsConfig {
3043    fn to_tokens(&self, tokens: &mut TokenStream) {
3044      let effects = vec_lit(self.effects.clone(), |d| d);
3045      let state = opt_lit(self.state.as_ref());
3046      let radius = opt_lit(self.radius.as_ref());
3047      let color = opt_lit(self.color.as_ref());
3048
3049      literal_struct!(
3050        tokens,
3051        ::tauri::utils::config::WindowEffectsConfig,
3052        effects,
3053        state,
3054        radius,
3055        color
3056      )
3057    }
3058  }
3059
3060  impl ToTokens for crate::TitleBarStyle {
3061    fn to_tokens(&self, tokens: &mut TokenStream) {
3062      let prefix = quote! { ::tauri::utils::TitleBarStyle };
3063
3064      tokens.append_all(match self {
3065        Self::Visible => quote! { #prefix::Visible },
3066        Self::Transparent => quote! { #prefix::Transparent },
3067        Self::Overlay => quote! { #prefix::Overlay },
3068      })
3069    }
3070  }
3071
3072  impl ToTokens for LogicalPosition {
3073    fn to_tokens(&self, tokens: &mut TokenStream) {
3074      let LogicalPosition { x, y } = self;
3075      literal_struct!(tokens, ::tauri::utils::config::LogicalPosition, x, y)
3076    }
3077  }
3078
3079  impl ToTokens for crate::WindowEffect {
3080    fn to_tokens(&self, tokens: &mut TokenStream) {
3081      let prefix = quote! { ::tauri::utils::WindowEffect };
3082
3083      #[allow(deprecated)]
3084      tokens.append_all(match self {
3085        WindowEffect::AppearanceBased => quote! { #prefix::AppearanceBased},
3086        WindowEffect::Light => quote! { #prefix::Light},
3087        WindowEffect::Dark => quote! { #prefix::Dark},
3088        WindowEffect::MediumLight => quote! { #prefix::MediumLight},
3089        WindowEffect::UltraDark => quote! { #prefix::UltraDark},
3090        WindowEffect::Titlebar => quote! { #prefix::Titlebar},
3091        WindowEffect::Selection => quote! { #prefix::Selection},
3092        WindowEffect::Menu => quote! { #prefix::Menu},
3093        WindowEffect::Popover => quote! { #prefix::Popover},
3094        WindowEffect::Sidebar => quote! { #prefix::Sidebar},
3095        WindowEffect::HeaderView => quote! { #prefix::HeaderView},
3096        WindowEffect::Sheet => quote! { #prefix::Sheet},
3097        WindowEffect::WindowBackground => quote! { #prefix::WindowBackground},
3098        WindowEffect::HudWindow => quote! { #prefix::HudWindow},
3099        WindowEffect::FullScreenUI => quote! { #prefix::FullScreenUI},
3100        WindowEffect::Tooltip => quote! { #prefix::Tooltip},
3101        WindowEffect::ContentBackground => quote! { #prefix::ContentBackground},
3102        WindowEffect::UnderWindowBackground => quote! { #prefix::UnderWindowBackground},
3103        WindowEffect::UnderPageBackground => quote! { #prefix::UnderPageBackground},
3104        WindowEffect::Mica => quote! { #prefix::Mica},
3105        WindowEffect::MicaDark => quote! { #prefix::MicaDark},
3106        WindowEffect::MicaLight => quote! { #prefix::MicaLight},
3107        WindowEffect::Blur => quote! { #prefix::Blur},
3108        WindowEffect::Acrylic => quote! { #prefix::Acrylic},
3109        WindowEffect::Tabbed => quote! { #prefix::Tabbed },
3110        WindowEffect::TabbedDark => quote! { #prefix::TabbedDark },
3111        WindowEffect::TabbedLight => quote! { #prefix::TabbedLight },
3112      })
3113    }
3114  }
3115
3116  impl ToTokens for crate::WindowEffectState {
3117    fn to_tokens(&self, tokens: &mut TokenStream) {
3118      let prefix = quote! { ::tauri::utils::WindowEffectState };
3119
3120      #[allow(deprecated)]
3121      tokens.append_all(match self {
3122        WindowEffectState::Active => quote! { #prefix::Active},
3123        WindowEffectState::FollowsWindowActiveState => quote! { #prefix::FollowsWindowActiveState},
3124        WindowEffectState::Inactive => quote! { #prefix::Inactive},
3125      })
3126    }
3127  }
3128
3129  impl ToTokens for PreventOverflowMargin {
3130    fn to_tokens(&self, tokens: &mut TokenStream) {
3131      let width = self.width;
3132      let height = self.height;
3133
3134      literal_struct!(
3135        tokens,
3136        ::tauri::utils::config::PreventOverflowMargin,
3137        width,
3138        height
3139      )
3140    }
3141  }
3142
3143  impl ToTokens for PreventOverflowConfig {
3144    fn to_tokens(&self, tokens: &mut TokenStream) {
3145      let prefix = quote! { ::tauri::utils::config::PreventOverflowConfig };
3146
3147      #[allow(deprecated)]
3148      tokens.append_all(match self {
3149        Self::Enable(enable) => quote! { #prefix::Enable(#enable) },
3150        Self::Margin(margin) => quote! { #prefix::Margin(#margin) },
3151      })
3152    }
3153  }
3154
3155  impl ToTokens for WindowConfig {
3156    fn to_tokens(&self, tokens: &mut TokenStream) {
3157      let label = str_lit(&self.label);
3158      let create = &self.create;
3159      let url = &self.url;
3160      let user_agent = opt_str_lit(self.user_agent.as_ref());
3161      let drag_drop_enabled = self.drag_drop_enabled;
3162      let center = self.center;
3163      let x = opt_lit(self.x.as_ref());
3164      let y = opt_lit(self.y.as_ref());
3165      let width = self.width;
3166      let height = self.height;
3167      let min_width = opt_lit(self.min_width.as_ref());
3168      let min_height = opt_lit(self.min_height.as_ref());
3169      let max_width = opt_lit(self.max_width.as_ref());
3170      let max_height = opt_lit(self.max_height.as_ref());
3171      let prevent_overflow = opt_lit(self.prevent_overflow.as_ref());
3172      let resizable = self.resizable;
3173      let maximizable = self.maximizable;
3174      let minimizable = self.minimizable;
3175      let closable = self.closable;
3176      let title = str_lit(&self.title);
3177      let proxy_url = opt_lit(self.proxy_url.as_ref().map(url_lit).as_ref());
3178      let fullscreen = self.fullscreen;
3179      let focus = self.focus;
3180      let transparent = self.transparent;
3181      let maximized = self.maximized;
3182      let visible = self.visible;
3183      let decorations = self.decorations;
3184      let always_on_bottom = self.always_on_bottom;
3185      let always_on_top = self.always_on_top;
3186      let visible_on_all_workspaces = self.visible_on_all_workspaces;
3187      let content_protected = self.content_protected;
3188      let skip_taskbar = self.skip_taskbar;
3189      let window_classname = opt_str_lit(self.window_classname.as_ref());
3190      let theme = opt_lit(self.theme.as_ref());
3191      let title_bar_style = &self.title_bar_style;
3192      let traffic_light_position = opt_lit(self.traffic_light_position.as_ref());
3193      let hidden_title = self.hidden_title;
3194      let accept_first_mouse = self.accept_first_mouse;
3195      let tabbing_identifier = opt_str_lit(self.tabbing_identifier.as_ref());
3196      let additional_browser_args = opt_str_lit(self.additional_browser_args.as_ref());
3197      let shadow = self.shadow;
3198      let window_effects = opt_lit(self.window_effects.as_ref());
3199      let incognito = self.incognito;
3200      let parent = opt_str_lit(self.parent.as_ref());
3201      let zoom_hotkeys_enabled = self.zoom_hotkeys_enabled;
3202      let browser_extensions_enabled = self.browser_extensions_enabled;
3203      let use_https_scheme = self.use_https_scheme;
3204      let devtools = opt_lit(self.devtools.as_ref());
3205      let background_color = opt_lit(self.background_color.as_ref());
3206      let background_throttling = opt_lit(self.background_throttling.as_ref());
3207      let javascript_disabled = self.javascript_disabled;
3208      let allow_link_preview = self.allow_link_preview;
3209      let disable_input_accessory_view = self.disable_input_accessory_view;
3210
3211      literal_struct!(
3212        tokens,
3213        ::tauri::utils::config::WindowConfig,
3214        label,
3215        url,
3216        create,
3217        user_agent,
3218        drag_drop_enabled,
3219        center,
3220        x,
3221        y,
3222        width,
3223        height,
3224        min_width,
3225        min_height,
3226        max_width,
3227        max_height,
3228        prevent_overflow,
3229        resizable,
3230        maximizable,
3231        minimizable,
3232        closable,
3233        title,
3234        proxy_url,
3235        fullscreen,
3236        focus,
3237        transparent,
3238        maximized,
3239        visible,
3240        decorations,
3241        always_on_bottom,
3242        always_on_top,
3243        visible_on_all_workspaces,
3244        content_protected,
3245        skip_taskbar,
3246        window_classname,
3247        theme,
3248        title_bar_style,
3249        traffic_light_position,
3250        hidden_title,
3251        accept_first_mouse,
3252        tabbing_identifier,
3253        additional_browser_args,
3254        shadow,
3255        window_effects,
3256        incognito,
3257        parent,
3258        zoom_hotkeys_enabled,
3259        browser_extensions_enabled,
3260        use_https_scheme,
3261        devtools,
3262        background_color,
3263        background_throttling,
3264        javascript_disabled,
3265        allow_link_preview,
3266        disable_input_accessory_view
3267      );
3268    }
3269  }
3270
3271  impl ToTokens for PatternKind {
3272    fn to_tokens(&self, tokens: &mut TokenStream) {
3273      let prefix = quote! { ::tauri::utils::config::PatternKind };
3274
3275      tokens.append_all(match self {
3276        Self::Brownfield => quote! { #prefix::Brownfield },
3277        #[cfg(not(feature = "isolation"))]
3278        Self::Isolation { dir: _ } => quote! { #prefix::Brownfield },
3279        #[cfg(feature = "isolation")]
3280        Self::Isolation { dir } => {
3281          let dir = path_buf_lit(dir);
3282          quote! { #prefix::Isolation { dir: #dir } }
3283        }
3284      })
3285    }
3286  }
3287
3288  impl ToTokens for WebviewInstallMode {
3289    fn to_tokens(&self, tokens: &mut TokenStream) {
3290      let prefix = quote! { ::tauri::utils::config::WebviewInstallMode };
3291
3292      tokens.append_all(match self {
3293        Self::Skip => quote! { #prefix::Skip },
3294        Self::DownloadBootstrapper { silent } => {
3295          quote! { #prefix::DownloadBootstrapper { silent: #silent } }
3296        }
3297        Self::EmbedBootstrapper { silent } => {
3298          quote! { #prefix::EmbedBootstrapper { silent: #silent } }
3299        }
3300        Self::OfflineInstaller { silent } => {
3301          quote! { #prefix::OfflineInstaller { silent: #silent } }
3302        }
3303        Self::FixedRuntime { path } => {
3304          let path = path_buf_lit(path);
3305          quote! { #prefix::FixedRuntime { path: #path } }
3306        }
3307      })
3308    }
3309  }
3310
3311  impl ToTokens for WindowsConfig {
3312    fn to_tokens(&self, tokens: &mut TokenStream) {
3313      let webview_install_mode = &self.webview_install_mode;
3314      tokens.append_all(quote! { ::tauri::utils::config::WindowsConfig {
3315        webview_install_mode: #webview_install_mode,
3316        ..Default::default()
3317      }})
3318    }
3319  }
3320
3321  impl ToTokens for BundleConfig {
3322    fn to_tokens(&self, tokens: &mut TokenStream) {
3323      let publisher = quote!(None);
3324      let homepage = quote!(None);
3325      let icon = vec_lit(&self.icon, str_lit);
3326      let active = self.active;
3327      let targets = quote!(Default::default());
3328      let create_updater_artifacts = quote!(Default::default());
3329      let resources = quote!(None);
3330      let copyright = quote!(None);
3331      let category = quote!(None);
3332      let file_associations = quote!(None);
3333      let short_description = quote!(None);
3334      let long_description = quote!(None);
3335      let use_local_tools_dir = self.use_local_tools_dir;
3336      let external_bin = opt_vec_lit(self.external_bin.as_ref(), str_lit);
3337      let windows = &self.windows;
3338      let license = opt_str_lit(self.license.as_ref());
3339      let license_file = opt_lit(self.license_file.as_ref().map(path_buf_lit).as_ref());
3340      let linux = quote!(Default::default());
3341      let macos = quote!(Default::default());
3342      let ios = quote!(Default::default());
3343      let android = quote!(Default::default());
3344
3345      literal_struct!(
3346        tokens,
3347        ::tauri::utils::config::BundleConfig,
3348        active,
3349        publisher,
3350        homepage,
3351        icon,
3352        targets,
3353        create_updater_artifacts,
3354        resources,
3355        copyright,
3356        category,
3357        license,
3358        license_file,
3359        file_associations,
3360        short_description,
3361        long_description,
3362        use_local_tools_dir,
3363        external_bin,
3364        windows,
3365        linux,
3366        macos,
3367        ios,
3368        android
3369      );
3370    }
3371  }
3372
3373  impl ToTokens for FrontendDist {
3374    fn to_tokens(&self, tokens: &mut TokenStream) {
3375      let prefix = quote! { ::tauri::utils::config::FrontendDist };
3376
3377      tokens.append_all(match self {
3378        Self::Url(url) => {
3379          let url = url_lit(url);
3380          quote! { #prefix::Url(#url) }
3381        }
3382        Self::Directory(path) => {
3383          let path = path_buf_lit(path);
3384          quote! { #prefix::Directory(#path) }
3385        }
3386        Self::Files(files) => {
3387          let files = vec_lit(files, path_buf_lit);
3388          quote! { #prefix::Files(#files) }
3389        }
3390      })
3391    }
3392  }
3393
3394  impl ToTokens for BuildConfig {
3395    fn to_tokens(&self, tokens: &mut TokenStream) {
3396      let dev_url = opt_lit(self.dev_url.as_ref().map(url_lit).as_ref());
3397      let frontend_dist = opt_lit(self.frontend_dist.as_ref());
3398      let runner = quote!(None);
3399      let before_dev_command = quote!(None);
3400      let before_build_command = quote!(None);
3401      let before_bundle_command = quote!(None);
3402      let features = quote!(None);
3403      let remove_unused_commands = quote!(false);
3404
3405      literal_struct!(
3406        tokens,
3407        ::tauri::utils::config::BuildConfig,
3408        runner,
3409        dev_url,
3410        frontend_dist,
3411        before_dev_command,
3412        before_build_command,
3413        before_bundle_command,
3414        features,
3415        remove_unused_commands
3416      );
3417    }
3418  }
3419
3420  impl ToTokens for CspDirectiveSources {
3421    fn to_tokens(&self, tokens: &mut TokenStream) {
3422      let prefix = quote! { ::tauri::utils::config::CspDirectiveSources };
3423
3424      tokens.append_all(match self {
3425        Self::Inline(sources) => {
3426          let sources = sources.as_str();
3427          quote!(#prefix::Inline(#sources.into()))
3428        }
3429        Self::List(list) => {
3430          let list = vec_lit(list, str_lit);
3431          quote!(#prefix::List(#list))
3432        }
3433      })
3434    }
3435  }
3436
3437  impl ToTokens for Csp {
3438    fn to_tokens(&self, tokens: &mut TokenStream) {
3439      let prefix = quote! { ::tauri::utils::config::Csp };
3440
3441      tokens.append_all(match self {
3442        Self::Policy(policy) => {
3443          let policy = policy.as_str();
3444          quote!(#prefix::Policy(#policy.into()))
3445        }
3446        Self::DirectiveMap(list) => {
3447          let map = map_lit(
3448            quote! { ::std::collections::HashMap },
3449            list,
3450            str_lit,
3451            identity,
3452          );
3453          quote!(#prefix::DirectiveMap(#map))
3454        }
3455      })
3456    }
3457  }
3458
3459  impl ToTokens for DisabledCspModificationKind {
3460    fn to_tokens(&self, tokens: &mut TokenStream) {
3461      let prefix = quote! { ::tauri::utils::config::DisabledCspModificationKind };
3462
3463      tokens.append_all(match self {
3464        Self::Flag(flag) => {
3465          quote! { #prefix::Flag(#flag) }
3466        }
3467        Self::List(directives) => {
3468          let directives = vec_lit(directives, str_lit);
3469          quote! { #prefix::List(#directives) }
3470        }
3471      });
3472    }
3473  }
3474
3475  impl ToTokens for CapabilityEntry {
3476    fn to_tokens(&self, tokens: &mut TokenStream) {
3477      let prefix = quote! { ::tauri::utils::config::CapabilityEntry };
3478
3479      tokens.append_all(match self {
3480        Self::Inlined(capability) => {
3481          quote! { #prefix::Inlined(#capability) }
3482        }
3483        Self::Reference(id) => {
3484          let id = str_lit(id);
3485          quote! { #prefix::Reference(#id) }
3486        }
3487      });
3488    }
3489  }
3490
3491  impl ToTokens for HeaderSource {
3492    fn to_tokens(&self, tokens: &mut TokenStream) {
3493      let prefix = quote! { ::tauri::utils::config::HeaderSource };
3494
3495      tokens.append_all(match self {
3496        Self::Inline(s) => {
3497          let line = s.as_str();
3498          quote!(#prefix::Inline(#line.into()))
3499        }
3500        Self::List(l) => {
3501          let list = vec_lit(l, str_lit);
3502          quote!(#prefix::List(#list))
3503        }
3504        Self::Map(m) => {
3505          let map = map_lit(quote! { ::std::collections::HashMap }, m, str_lit, str_lit);
3506          quote!(#prefix::Map(#map))
3507        }
3508      })
3509    }
3510  }
3511
3512  impl ToTokens for HeaderConfig {
3513    fn to_tokens(&self, tokens: &mut TokenStream) {
3514      let access_control_allow_credentials =
3515        opt_lit(self.access_control_allow_credentials.as_ref());
3516      let access_control_allow_headers = opt_lit(self.access_control_allow_headers.as_ref());
3517      let access_control_allow_methods = opt_lit(self.access_control_allow_methods.as_ref());
3518      let access_control_expose_headers = opt_lit(self.access_control_expose_headers.as_ref());
3519      let access_control_max_age = opt_lit(self.access_control_max_age.as_ref());
3520      let cross_origin_embedder_policy = opt_lit(self.cross_origin_embedder_policy.as_ref());
3521      let cross_origin_opener_policy = opt_lit(self.cross_origin_opener_policy.as_ref());
3522      let cross_origin_resource_policy = opt_lit(self.cross_origin_resource_policy.as_ref());
3523      let permissions_policy = opt_lit(self.permissions_policy.as_ref());
3524      let timing_allow_origin = opt_lit(self.timing_allow_origin.as_ref());
3525      let x_content_type_options = opt_lit(self.x_content_type_options.as_ref());
3526      let tauri_custom_header = opt_lit(self.tauri_custom_header.as_ref());
3527
3528      literal_struct!(
3529        tokens,
3530        ::tauri::utils::config::HeaderConfig,
3531        access_control_allow_credentials,
3532        access_control_allow_headers,
3533        access_control_allow_methods,
3534        access_control_expose_headers,
3535        access_control_max_age,
3536        cross_origin_embedder_policy,
3537        cross_origin_opener_policy,
3538        cross_origin_resource_policy,
3539        permissions_policy,
3540        timing_allow_origin,
3541        x_content_type_options,
3542        tauri_custom_header
3543      );
3544    }
3545  }
3546
3547  impl ToTokens for SecurityConfig {
3548    fn to_tokens(&self, tokens: &mut TokenStream) {
3549      let csp = opt_lit(self.csp.as_ref());
3550      let dev_csp = opt_lit(self.dev_csp.as_ref());
3551      let freeze_prototype = self.freeze_prototype;
3552      let dangerous_disable_asset_csp_modification = &self.dangerous_disable_asset_csp_modification;
3553      let asset_protocol = &self.asset_protocol;
3554      let pattern = &self.pattern;
3555      let capabilities = vec_lit(&self.capabilities, identity);
3556      let headers = opt_lit(self.headers.as_ref());
3557
3558      literal_struct!(
3559        tokens,
3560        ::tauri::utils::config::SecurityConfig,
3561        csp,
3562        dev_csp,
3563        freeze_prototype,
3564        dangerous_disable_asset_csp_modification,
3565        asset_protocol,
3566        pattern,
3567        capabilities,
3568        headers
3569      );
3570    }
3571  }
3572
3573  impl ToTokens for TrayIconConfig {
3574    fn to_tokens(&self, tokens: &mut TokenStream) {
3575      // For [`Self::menu_on_left_click`]
3576      tokens.append_all(quote!(#[allow(deprecated)]));
3577
3578      let id = opt_str_lit(self.id.as_ref());
3579      let icon_as_template = self.icon_as_template;
3580      #[allow(deprecated)]
3581      let menu_on_left_click = self.menu_on_left_click;
3582      let show_menu_on_left_click = self.show_menu_on_left_click;
3583      let icon_path = path_buf_lit(&self.icon_path);
3584      let title = opt_str_lit(self.title.as_ref());
3585      let tooltip = opt_str_lit(self.tooltip.as_ref());
3586      literal_struct!(
3587        tokens,
3588        ::tauri::utils::config::TrayIconConfig,
3589        id,
3590        icon_path,
3591        icon_as_template,
3592        menu_on_left_click,
3593        show_menu_on_left_click,
3594        title,
3595        tooltip
3596      );
3597    }
3598  }
3599
3600  impl ToTokens for FsScope {
3601    fn to_tokens(&self, tokens: &mut TokenStream) {
3602      let prefix = quote! { ::tauri::utils::config::FsScope };
3603
3604      tokens.append_all(match self {
3605        Self::AllowedPaths(allow) => {
3606          let allowed_paths = vec_lit(allow, path_buf_lit);
3607          quote! { #prefix::AllowedPaths(#allowed_paths) }
3608        }
3609        Self::Scope { allow, deny , require_literal_leading_dot} => {
3610          let allow = vec_lit(allow, path_buf_lit);
3611          let deny = vec_lit(deny, path_buf_lit);
3612          let  require_literal_leading_dot = opt_lit(require_literal_leading_dot.as_ref());
3613          quote! { #prefix::Scope { allow: #allow, deny: #deny, require_literal_leading_dot: #require_literal_leading_dot } }
3614        }
3615      });
3616    }
3617  }
3618
3619  impl ToTokens for AssetProtocolConfig {
3620    fn to_tokens(&self, tokens: &mut TokenStream) {
3621      let scope = &self.scope;
3622      tokens.append_all(quote! { ::tauri::utils::config::AssetProtocolConfig { scope: #scope, ..Default::default() } })
3623    }
3624  }
3625
3626  impl ToTokens for AppConfig {
3627    fn to_tokens(&self, tokens: &mut TokenStream) {
3628      let windows = vec_lit(&self.windows, identity);
3629      let security = &self.security;
3630      let tray_icon = opt_lit(self.tray_icon.as_ref());
3631      let macos_private_api = self.macos_private_api;
3632      let with_global_tauri = self.with_global_tauri;
3633      let enable_gtk_app_id = self.enable_gtk_app_id;
3634
3635      literal_struct!(
3636        tokens,
3637        ::tauri::utils::config::AppConfig,
3638        windows,
3639        security,
3640        tray_icon,
3641        macos_private_api,
3642        with_global_tauri,
3643        enable_gtk_app_id
3644      );
3645    }
3646  }
3647
3648  impl ToTokens for PluginConfig {
3649    fn to_tokens(&self, tokens: &mut TokenStream) {
3650      let config = map_lit(
3651        quote! { ::std::collections::HashMap },
3652        &self.0,
3653        str_lit,
3654        json_value_lit,
3655      );
3656      tokens.append_all(quote! { ::tauri::utils::config::PluginConfig(#config) })
3657    }
3658  }
3659
3660  impl ToTokens for Config {
3661    fn to_tokens(&self, tokens: &mut TokenStream) {
3662      let schema = quote!(None);
3663      let product_name = opt_str_lit(self.product_name.as_ref());
3664      let main_binary_name = opt_str_lit(self.main_binary_name.as_ref());
3665      let version = opt_str_lit(self.version.as_ref());
3666      let identifier = str_lit(&self.identifier);
3667      let app = &self.app;
3668      let build = &self.build;
3669      let bundle = &self.bundle;
3670      let plugins = &self.plugins;
3671
3672      literal_struct!(
3673        tokens,
3674        ::tauri::utils::config::Config,
3675        schema,
3676        product_name,
3677        main_binary_name,
3678        version,
3679        identifier,
3680        app,
3681        build,
3682        bundle,
3683        plugins
3684      );
3685    }
3686  }
3687}
3688
3689#[cfg(test)]
3690mod test {
3691  use super::*;
3692
3693  // TODO: create a test that compares a config to a json config
3694
3695  #[test]
3696  // test all of the default functions
3697  fn test_defaults() {
3698    // get default app config
3699    let a_config = AppConfig::default();
3700    // get default build config
3701    let b_config = BuildConfig::default();
3702    // get default window
3703    let d_windows: Vec<WindowConfig> = vec![];
3704    // get default bundle
3705    let d_bundle = BundleConfig::default();
3706
3707    // create a tauri config.
3708    let app = AppConfig {
3709      windows: vec![],
3710      security: SecurityConfig {
3711        csp: None,
3712        dev_csp: None,
3713        freeze_prototype: false,
3714        dangerous_disable_asset_csp_modification: DisabledCspModificationKind::Flag(false),
3715        asset_protocol: AssetProtocolConfig::default(),
3716        pattern: Default::default(),
3717        capabilities: Vec::new(),
3718        headers: None,
3719      },
3720      tray_icon: None,
3721      macos_private_api: false,
3722      with_global_tauri: false,
3723      enable_gtk_app_id: false,
3724    };
3725
3726    // create a build config
3727    let build = BuildConfig {
3728      runner: None,
3729      dev_url: None,
3730      frontend_dist: None,
3731      before_dev_command: None,
3732      before_build_command: None,
3733      before_bundle_command: None,
3734      features: None,
3735      remove_unused_commands: false,
3736    };
3737
3738    // create a bundle config
3739    let bundle = BundleConfig {
3740      active: false,
3741      targets: Default::default(),
3742      create_updater_artifacts: Default::default(),
3743      publisher: None,
3744      homepage: None,
3745      icon: Vec::new(),
3746      resources: None,
3747      copyright: None,
3748      category: None,
3749      file_associations: None,
3750      short_description: None,
3751      long_description: None,
3752      use_local_tools_dir: false,
3753      license: None,
3754      license_file: None,
3755      linux: Default::default(),
3756      macos: Default::default(),
3757      external_bin: None,
3758      windows: Default::default(),
3759      ios: Default::default(),
3760      android: Default::default(),
3761    };
3762
3763    // test the configs
3764    assert_eq!(a_config, app);
3765    assert_eq!(b_config, build);
3766    assert_eq!(d_bundle, bundle);
3767    assert_eq!(d_windows, app.windows);
3768  }
3769
3770  #[test]
3771  fn parse_hex_color() {
3772    use super::Color;
3773
3774    assert_eq!(Color(255, 255, 255, 255), "fff".parse().unwrap());
3775    assert_eq!(Color(255, 255, 255, 255), "#fff".parse().unwrap());
3776    assert_eq!(Color(0, 0, 0, 255), "#000000".parse().unwrap());
3777    assert_eq!(Color(0, 0, 0, 255), "#000000ff".parse().unwrap());
3778    assert_eq!(Color(0, 255, 0, 255), "#00ff00ff".parse().unwrap());
3779  }
3780}