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