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