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