Skip to main content

tauri_utils/
config.rs

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