tauri_utils/
config.rs

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