Skip to main content

tauri_utils/
config.rs

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