1#[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
46pub 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#[derive(PartialEq, Eq, Debug, Clone, Serialize)]
59#[cfg_attr(feature = "schema", derive(JsonSchema))]
60#[serde(untagged)]
61#[non_exhaustive]
62pub enum WebviewUrl {
63 External(Url),
65 App(PathBuf),
69 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#[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 Deb,
120 Rpm,
122 AppImage,
124 Msi,
126 Nsis,
128 App,
130 Dmg,
132}
133
134impl BundleType {
135 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#[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 #[default]
205 All,
206 #[cfg_attr(feature = "schema", schemars(untagged))]
207 List(Vec<BundleType>),
209 #[cfg_attr(feature = "schema", schemars(untagged))]
210 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 #[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#[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 #[serde(default, alias = "bundle-media-framework")]
278 pub bundle_media_framework: bool,
279 #[serde(default)]
281 pub files: HashMap<PathBuf, PathBuf>,
282}
283
284const ZSTD_MIN_CLEVEL: i32 = -1 << 17;
285
286#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
288#[cfg_attr(feature = "schema", derive(JsonSchema))]
289#[serde(rename_all = "camelCase", deny_unknown_fields, tag = "type")]
290#[non_exhaustive]
291pub enum DebCompression {
292 Gzip {
294 level: u32,
296 },
297 Zstd {
299 level: i32,
301 },
302 Xz {
304 level: u32,
306 },
307 Bzip2 {
309 level: u32,
311 },
312 None,
314}
315
316impl DebCompression {
317 pub fn ext(&self) -> &'static str {
319 match self {
320 Self::None => "tar",
321 Self::Gzip { .. } => "tar.gz",
322 Self::Xz { .. } => "tar.xz",
323 Self::Zstd { .. } => "tar.zst",
324 Self::Bzip2 { .. } => "tar.bz2",
325 }
326 }
327
328 pub fn default_level(&self) -> Option<i32> {
330 match self {
331 Self::None => None,
332 Self::Gzip { .. } => Some(9),
333 Self::Xz { .. } => Some(9),
334 Self::Zstd { .. } => Some(19),
335 Self::Bzip2 { .. } => Some(9),
336 }
337 }
338
339 pub fn min_level(&self) -> Option<i32> {
341 match self {
342 Self::None => None,
343 Self::Gzip { .. } => Some(0),
344 Self::Xz { .. } => Some(0),
345 Self::Zstd { .. } => Some(ZSTD_MIN_CLEVEL),
346 Self::Bzip2 { .. } => Some(1),
348 }
349 }
350
351 pub fn max_level(&self) -> Option<i32> {
353 match self {
354 Self::None => None,
355 Self::Gzip { .. } => Some(9),
356 Self::Xz { .. } => Some(9),
357 Self::Zstd { .. } => Some(22),
358 Self::Bzip2 { .. } => Some(9),
359 }
360 }
361
362 pub fn level(&self) -> Option<i32> {
364 match self {
365 Self::None => None,
366 Self::Gzip { level } | Self::Xz { level } | Self::Bzip2 { level } => {
367 Some(i32::try_from(*level).unwrap_or(i32::MAX))
368 }
369 Self::Zstd { level } => Some(*level),
370 }
371 }
372}
373
374#[skip_serializing_none]
378#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
379#[cfg_attr(feature = "schema", derive(JsonSchema))]
380#[serde(rename_all = "camelCase", deny_unknown_fields)]
381pub struct DebConfig {
382 pub depends: Option<Vec<String>>,
384 pub recommends: Option<Vec<String>>,
386 pub provides: Option<Vec<String>>,
388 pub conflicts: Option<Vec<String>>,
390 pub replaces: Option<Vec<String>>,
392 #[serde(default)]
394 pub files: HashMap<PathBuf, PathBuf>,
395 pub section: Option<String>,
397 pub priority: Option<String>,
400 pub changelog: Option<PathBuf>,
403 #[serde(alias = "desktop-template")]
407 pub desktop_template: Option<PathBuf>,
408 #[serde(alias = "pre-install-script")]
411 pub pre_install_script: Option<PathBuf>,
412 #[serde(alias = "post-install-script")]
415 pub post_install_script: Option<PathBuf>,
416 #[serde(alias = "pre-remove-script")]
419 pub pre_remove_script: Option<PathBuf>,
420 #[serde(alias = "post-remove-script")]
423 pub post_remove_script: Option<PathBuf>,
424 pub compression: Option<DebCompression>,
426}
427
428#[skip_serializing_none]
432#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
433#[cfg_attr(feature = "schema", derive(JsonSchema))]
434#[serde(rename_all = "camelCase", deny_unknown_fields)]
435pub struct LinuxConfig {
436 #[serde(default)]
438 pub appimage: AppImageConfig,
439 #[serde(default)]
441 pub deb: DebConfig,
442 #[serde(default)]
444 pub rpm: RpmConfig,
445}
446
447#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
449#[cfg_attr(feature = "schema", derive(JsonSchema))]
450#[serde(rename_all = "camelCase", deny_unknown_fields, tag = "type")]
451#[non_exhaustive]
452pub enum RpmCompression {
453 Gzip {
455 level: u32,
457 },
458 Zstd {
460 level: i32,
462 },
463 Xz {
465 level: u32,
467 },
468 Bzip2 {
470 level: u32,
472 },
473 None,
475}
476
477impl RpmCompression {
478 pub fn default_level(&self) -> Option<i32> {
480 match self {
481 Self::None => None,
482 Self::Gzip { .. } => Some(9),
483 Self::Xz { .. } => Some(9),
484 Self::Zstd { .. } => Some(19),
485 Self::Bzip2 { .. } => Some(9),
486 }
487 }
488
489 pub fn min_level(&self) -> Option<i32> {
491 match self {
492 Self::None => None,
493 Self::Gzip { .. } => Some(0),
494 Self::Xz { .. } => Some(0),
495 Self::Zstd { .. } => Some(ZSTD_MIN_CLEVEL),
496 Self::Bzip2 { .. } => Some(1),
498 }
499 }
500
501 pub fn max_level(&self) -> Option<i32> {
503 match self {
504 Self::None => None,
505 Self::Gzip { .. } => Some(9),
506 Self::Xz { .. } => Some(9),
507 Self::Zstd { .. } => Some(22),
508 Self::Bzip2 { .. } => Some(9),
509 }
510 }
511
512 pub fn level(&self) -> Option<i32> {
514 match self {
515 Self::None => None,
516 Self::Gzip { level } | Self::Xz { level } | Self::Bzip2 { level } => {
517 Some(i32::try_from(*level).unwrap_or(i32::MAX))
518 }
519 Self::Zstd { level } => Some(*level),
520 }
521 }
522}
523
524#[skip_serializing_none]
526#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
527#[cfg_attr(feature = "schema", derive(JsonSchema))]
528#[serde(rename_all = "camelCase", deny_unknown_fields)]
529pub struct RpmConfig {
530 pub depends: Option<Vec<String>>,
532 pub recommends: Option<Vec<String>>,
534 pub provides: Option<Vec<String>>,
536 pub conflicts: Option<Vec<String>>,
539 pub obsoletes: Option<Vec<String>>,
542 #[serde(default = "default_release")]
544 pub release: String,
545 #[serde(default)]
547 pub epoch: u32,
548 #[serde(default)]
550 pub files: HashMap<PathBuf, PathBuf>,
551 #[serde(alias = "desktop-template")]
555 pub desktop_template: Option<PathBuf>,
556 #[serde(alias = "pre-install-script")]
559 pub pre_install_script: Option<PathBuf>,
560 #[serde(alias = "post-install-script")]
563 pub post_install_script: Option<PathBuf>,
564 #[serde(alias = "pre-remove-script")]
567 pub pre_remove_script: Option<PathBuf>,
568 #[serde(alias = "post-remove-script")]
571 pub post_remove_script: Option<PathBuf>,
572 pub compression: Option<RpmCompression>,
574}
575
576impl Default for RpmConfig {
577 fn default() -> Self {
578 Self {
579 depends: None,
580 recommends: None,
581 provides: None,
582 conflicts: None,
583 obsoletes: None,
584 release: default_release(),
585 epoch: 0,
586 files: Default::default(),
587 desktop_template: None,
588 pre_install_script: None,
589 post_install_script: None,
590 pre_remove_script: None,
591 post_remove_script: None,
592 compression: None,
593 }
594 }
595}
596
597fn default_release() -> String {
598 "1".into()
599}
600
601#[derive(Default, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
603#[cfg_attr(feature = "schema", derive(JsonSchema))]
604#[serde(rename_all = "camelCase", deny_unknown_fields)]
605pub struct Position {
606 pub x: u32,
608 pub y: u32,
610}
611
612#[derive(Default, Debug, PartialEq, Clone, Deserialize, Serialize)]
614#[cfg_attr(feature = "schema", derive(JsonSchema))]
615#[serde(rename_all = "camelCase", deny_unknown_fields)]
616pub struct LogicalPosition {
617 pub x: f64,
619 pub y: f64,
621}
622
623#[derive(Default, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
625#[cfg_attr(feature = "schema", derive(JsonSchema))]
626#[serde(rename_all = "camelCase", deny_unknown_fields)]
627pub struct Size {
628 pub width: u32,
630 pub height: u32,
632}
633
634#[skip_serializing_none]
638#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
639#[cfg_attr(feature = "schema", derive(JsonSchema))]
640#[serde(rename_all = "camelCase", deny_unknown_fields)]
641pub struct DmgConfig {
642 pub background: Option<PathBuf>,
644 pub window_position: Option<Position>,
646 #[serde(default = "dmg_window_size", alias = "window-size")]
648 pub window_size: Size,
649 #[serde(default = "dmg_app_position", alias = "app-position")]
651 pub app_position: Position,
652 #[serde(
654 default = "dmg_application_folder_position",
655 alias = "application-folder-position"
656 )]
657 pub application_folder_position: Position,
658}
659
660impl Default for DmgConfig {
661 fn default() -> Self {
662 Self {
663 background: None,
664 window_position: None,
665 window_size: dmg_window_size(),
666 app_position: dmg_app_position(),
667 application_folder_position: dmg_application_folder_position(),
668 }
669 }
670}
671
672fn dmg_window_size() -> Size {
673 Size {
674 width: 660,
675 height: 400,
676 }
677}
678
679fn dmg_app_position() -> Position {
680 Position { x: 180, y: 170 }
681}
682
683fn dmg_application_folder_position() -> Position {
684 Position { x: 480, y: 170 }
685}
686
687fn de_macos_minimum_system_version<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
688where
689 D: Deserializer<'de>,
690{
691 let version = Option::<String>::deserialize(deserializer)?;
692 match version {
693 Some(v) if v.is_empty() => Ok(macos_minimum_system_version()),
694 e => Ok(e),
695 }
696}
697
698#[skip_serializing_none]
702#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
703#[cfg_attr(feature = "schema", derive(JsonSchema))]
704#[serde(rename_all = "camelCase", deny_unknown_fields)]
705pub struct MacConfig {
706 pub frameworks: Option<Vec<String>>,
710 #[serde(default)]
712 pub files: HashMap<PathBuf, PathBuf>,
713 #[serde(alias = "bundle-version")]
717 pub bundle_version: Option<String>,
718 #[serde(alias = "bundle-name")]
724 pub bundle_name: Option<String>,
725 #[serde(
734 deserialize_with = "de_macos_minimum_system_version",
735 default = "macos_minimum_system_version",
736 alias = "minimum-system-version"
737 )]
738 pub minimum_system_version: Option<String>,
739 #[serde(alias = "exception-domain")]
742 pub exception_domain: Option<String>,
743 #[serde(alias = "signing-identity")]
745 pub signing_identity: Option<String>,
746 #[serde(alias = "hardened-runtime", default = "default_true")]
748 pub hardened_runtime: bool,
749 #[serde(alias = "provider-short-name")]
751 pub provider_short_name: Option<String>,
752 pub entitlements: Option<String>,
754 #[serde(alias = "info-plist")]
758 pub info_plist: Option<PathBuf>,
759 #[serde(default)]
761 pub dmg: DmgConfig,
762}
763
764impl Default for MacConfig {
765 fn default() -> Self {
766 Self {
767 frameworks: None,
768 files: HashMap::new(),
769 bundle_version: None,
770 bundle_name: None,
771 minimum_system_version: macos_minimum_system_version(),
772 exception_domain: None,
773 signing_identity: None,
774 hardened_runtime: true,
775 provider_short_name: None,
776 entitlements: None,
777 info_plist: None,
778 dmg: Default::default(),
779 }
780 }
781}
782
783fn macos_minimum_system_version() -> Option<String> {
784 Some("10.13".into())
785}
786
787fn ios_minimum_system_version() -> String {
788 "15.0".into()
789}
790
791#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
795#[cfg_attr(feature = "schema", derive(JsonSchema))]
796#[serde(rename_all = "camelCase", deny_unknown_fields)]
797pub struct WixLanguageConfig {
798 #[serde(alias = "locale-path")]
800 pub locale_path: Option<String>,
801}
802
803#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
805#[cfg_attr(feature = "schema", derive(JsonSchema))]
806#[serde(untagged)]
807pub enum WixLanguage {
808 One(String),
810 List(Vec<String>),
812 Localized(HashMap<String, WixLanguageConfig>),
814}
815
816impl Default for WixLanguage {
817 fn default() -> Self {
818 Self::One("en-US".into())
819 }
820}
821
822#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
826#[cfg_attr(feature = "schema", derive(JsonSchema))]
827#[serde(rename_all = "camelCase", deny_unknown_fields)]
828pub struct WixConfig {
829 pub version: Option<String>,
838 #[serde(alias = "upgrade-code")]
847 pub upgrade_code: Option<uuid::Uuid>,
848 #[serde(default)]
850 pub language: WixLanguage,
851 pub template: Option<PathBuf>,
853 #[serde(default, alias = "fragment-paths")]
855 pub fragment_paths: Vec<PathBuf>,
856 #[serde(default, alias = "component-group-refs")]
858 pub component_group_refs: Vec<String>,
859 #[serde(default, alias = "component-refs")]
861 pub component_refs: Vec<String>,
862 #[serde(default, alias = "feature-group-refs")]
864 pub feature_group_refs: Vec<String>,
865 #[serde(default, alias = "feature-refs")]
867 pub feature_refs: Vec<String>,
868 #[serde(default, alias = "merge-refs")]
870 pub merge_refs: Vec<String>,
871 #[serde(default, alias = "enable-elevated-update-task")]
873 pub enable_elevated_update_task: bool,
874 #[serde(alias = "banner-path")]
879 pub banner_path: Option<PathBuf>,
880 #[serde(alias = "dialog-image-path")]
885 pub dialog_image_path: Option<PathBuf>,
886 #[serde(default, alias = "fips-compliant")]
889 pub fips_compliant: bool,
890}
891
892#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Default)]
896#[cfg_attr(feature = "schema", derive(JsonSchema))]
897#[serde(rename_all = "camelCase", deny_unknown_fields)]
898pub enum NsisCompression {
899 Zlib,
901 Bzip2,
903 #[default]
905 Lzma,
906 None,
908}
909
910#[derive(Default, Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
912#[serde(rename_all = "camelCase", deny_unknown_fields)]
913#[cfg_attr(feature = "schema", derive(JsonSchema))]
914pub enum NSISInstallerMode {
915 #[default]
921 CurrentUser,
922 PerMachine,
927 Both,
933}
934
935#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
937#[cfg_attr(feature = "schema", derive(JsonSchema))]
938#[serde(rename_all = "camelCase", deny_unknown_fields)]
939pub struct NsisConfig {
940 pub template: Option<PathBuf>,
942 #[serde(alias = "header-image")]
946 pub header_image: Option<PathBuf>,
947 #[serde(alias = "sidebar-image")]
951 pub sidebar_image: Option<PathBuf>,
952 #[serde(alias = "installer-icon")]
954 pub installer_icon: Option<PathBuf>,
955 #[serde(alias = "uninstaller-icon")]
957 pub uninstaller_icon: Option<PathBuf>,
958 #[serde(alias = "uninstaller-header-image")]
963 pub uninstaller_header_image: Option<PathBuf>,
964 #[serde(default, alias = "install-mode")]
966 pub install_mode: NSISInstallerMode,
967 pub languages: Option<Vec<String>>,
974 pub custom_language_files: Option<HashMap<String, PathBuf>>,
981 #[serde(default, alias = "display-language-selector")]
984 pub display_language_selector: bool,
985 #[serde(default)]
989 pub compression: NsisCompression,
990 #[serde(alias = "start-menu-folder")]
999 pub start_menu_folder: Option<String>,
1000 #[serde(alias = "installer-hooks")]
1030 pub installer_hooks: Option<PathBuf>,
1031 #[deprecated(
1037 since = "2.10.0",
1038 note = "Use `WindowsConfig::minimum_webview2_version` instead."
1039 )]
1040 #[serde(alias = "minimum-webview2-version")]
1041 pub minimum_webview2_version: Option<String>,
1042}
1043
1044#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
1049#[serde(tag = "type", rename_all = "camelCase", deny_unknown_fields)]
1050#[cfg_attr(feature = "schema", derive(JsonSchema))]
1051pub enum WebviewInstallMode {
1052 Skip,
1054 DownloadBootstrapper {
1058 #[serde(default = "default_true")]
1060 silent: bool,
1061 },
1062 EmbedBootstrapper {
1066 #[serde(default = "default_true")]
1068 silent: bool,
1069 },
1070 OfflineInstaller {
1074 #[serde(default = "default_true")]
1076 silent: bool,
1077 },
1078 FixedRuntime {
1081 path: PathBuf,
1086 },
1087}
1088
1089impl Default for WebviewInstallMode {
1090 fn default() -> Self {
1091 Self::DownloadBootstrapper { silent: true }
1092 }
1093}
1094
1095#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1097#[cfg_attr(feature = "schema", derive(JsonSchema))]
1098#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
1099pub enum CustomSignCommandConfig {
1100 Command(String),
1109 CommandWithOptions {
1114 cmd: String,
1116 args: Vec<String>,
1120 },
1121}
1122
1123#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1127#[cfg_attr(feature = "schema", derive(JsonSchema))]
1128#[serde(rename_all = "camelCase", deny_unknown_fields)]
1129pub struct WindowsConfig {
1130 #[serde(alias = "digest-algorithm")]
1133 pub digest_algorithm: Option<String>,
1134 #[serde(alias = "certificate-thumbprint")]
1136 pub certificate_thumbprint: Option<String>,
1137 #[serde(alias = "timestamp-url")]
1139 pub timestamp_url: Option<String>,
1140 #[serde(default)]
1143 pub tsp: bool,
1144 #[serde(default, alias = "webview-install-mode")]
1146 pub webview_install_mode: WebviewInstallMode,
1147 #[serde(default = "default_true", alias = "allow-downgrades")]
1157 pub allow_downgrades: bool,
1158 #[serde(alias = "minimum-webview2-version")]
1162 pub minimum_webview2_version: Option<String>,
1163 pub wix: Option<WixConfig>,
1165 pub nsis: Option<NsisConfig>,
1167 #[serde(alias = "sign-command")]
1175 pub sign_command: Option<CustomSignCommandConfig>,
1176 #[serde(
1183 default,
1184 rename = "bundleVCRuntime",
1185 alias = "bundle-vc-runtime",
1186 alias = "bundleVcRuntime"
1187 )]
1188 pub bundle_vc_runtime: bool,
1189}
1190
1191impl Default for WindowsConfig {
1192 fn default() -> Self {
1193 Self {
1194 digest_algorithm: None,
1195 certificate_thumbprint: None,
1196 timestamp_url: None,
1197 tsp: false,
1198 webview_install_mode: Default::default(),
1199 allow_downgrades: true,
1200 minimum_webview2_version: None,
1201 wix: None,
1202 nsis: None,
1203 sign_command: None,
1204 bundle_vc_runtime: false,
1205 }
1206 }
1207}
1208
1209#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1211#[cfg_attr(feature = "schema", derive(JsonSchema))]
1212pub enum BundleTypeRole {
1213 #[default]
1215 Editor,
1216 Viewer,
1218 Shell,
1220 QLGenerator,
1222 None,
1224}
1225
1226impl Display for BundleTypeRole {
1227 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1228 match self {
1229 Self::Editor => write!(f, "Editor"),
1230 Self::Viewer => write!(f, "Viewer"),
1231 Self::Shell => write!(f, "Shell"),
1232 Self::QLGenerator => write!(f, "QLGenerator"),
1233 Self::None => write!(f, "None"),
1234 }
1235 }
1236}
1237
1238#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1242#[cfg_attr(feature = "schema", derive(JsonSchema))]
1243pub enum HandlerRank {
1244 #[default]
1246 Default,
1247 Owner,
1249 Alternate,
1251 None,
1253}
1254
1255impl Display for HandlerRank {
1256 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1257 match self {
1258 Self::Default => write!(f, "Default"),
1259 Self::Owner => write!(f, "Owner"),
1260 Self::Alternate => write!(f, "Alternate"),
1261 Self::None => write!(f, "None"),
1262 }
1263 }
1264}
1265
1266#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
1270#[cfg_attr(feature = "schema", derive(JsonSchema))]
1271pub struct AssociationExt(pub String);
1272
1273impl fmt::Display for AssociationExt {
1274 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1275 write!(f, "{}", self.0)
1276 }
1277}
1278
1279impl<'d> serde::Deserialize<'d> for AssociationExt {
1280 fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {
1281 let ext = String::deserialize(deserializer)?;
1282 if let Some(ext) = ext.strip_prefix('.') {
1283 Ok(AssociationExt(ext.into()))
1284 } else {
1285 Ok(AssociationExt(ext))
1286 }
1287 }
1288}
1289
1290#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1292#[cfg_attr(feature = "schema", derive(JsonSchema))]
1293#[serde(rename_all = "camelCase", deny_unknown_fields)]
1294pub struct FileAssociation {
1295 pub ext: Vec<AssociationExt>,
1297 #[serde(alias = "content-types")]
1302 pub content_types: Option<Vec<String>>,
1303 pub name: Option<String>,
1305 pub description: Option<String>,
1307 #[serde(default)]
1309 pub role: BundleTypeRole,
1310 #[serde(alias = "mime-type")]
1318 pub mime_type: Option<String>,
1319 #[serde(default)]
1321 pub rank: HandlerRank,
1322 pub exported_type: Option<ExportedFileAssociation>,
1326 #[serde(alias = "android-intent-action-filters")]
1330 pub android_intent_action_filters: Option<Vec<AndroidIntentAction>>,
1331}
1332
1333#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Hash)]
1335#[cfg_attr(feature = "schema", derive(JsonSchema))]
1336#[serde(rename_all = "camelCase")]
1337#[non_exhaustive]
1338pub enum AndroidIntentAction {
1339 Send,
1343 SendMultiple,
1347 View,
1351}
1352
1353#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1355#[cfg_attr(feature = "schema", derive(JsonSchema))]
1356#[serde(rename_all = "camelCase", deny_unknown_fields)]
1357pub struct ExportedFileAssociation {
1358 pub identifier: String,
1360 #[serde(alias = "conforms-to")]
1364 pub conforms_to: Option<Vec<String>>,
1365}
1366
1367impl FileAssociation {
1368 pub fn infer_content_types(&self) -> HashSet<String> {
1375 let mut content_types = HashSet::new();
1376
1377 if let Some(exported_type) = &self.exported_type {
1379 content_types.insert(exported_type.identifier.clone());
1380 return content_types;
1381 }
1382
1383 if let Some(explicit_types) = &self.content_types {
1385 content_types.extend(explicit_types.iter().cloned());
1386 }
1387
1388 for ext in &self.ext {
1390 if let Some(uti) = extension_to_uti(&ext.0) {
1391 content_types.insert(uti.to_string());
1392 }
1393 }
1394
1395 if let Some(mime_type) = &self.mime_type
1397 && let Some(uti) = mime_type_to_uti(mime_type)
1398 {
1399 content_types.insert(uti.to_string());
1400 }
1401
1402 content_types
1403 }
1404}
1405
1406pub fn file_associations_plist(associations: &[FileAssociation]) -> Option<plist::Value> {
1412 use plist::{Dictionary, Value};
1413
1414 if associations.is_empty() {
1415 return None;
1416 }
1417
1418 let exported_associations = associations
1419 .iter()
1420 .filter_map(|association| {
1421 association.exported_type.as_ref().map(|exported_type| {
1422 let mut dict = Dictionary::new();
1423
1424 dict.insert(
1425 "UTTypeIdentifier".into(),
1426 exported_type.identifier.clone().into(),
1427 );
1428 if let Some(description) = &association.description {
1429 dict.insert("UTTypeDescription".into(), description.clone().into());
1430 }
1431 if let Some(conforms_to) = &exported_type.conforms_to {
1432 dict.insert(
1433 "UTTypeConformsTo".into(),
1434 Value::Array(conforms_to.iter().map(|s| s.clone().into()).collect()),
1435 );
1436 }
1437
1438 let mut specification = Dictionary::new();
1439 specification.insert(
1440 "public.filename-extension".into(),
1441 Value::Array(
1442 association
1443 .ext
1444 .iter()
1445 .map(|s| s.to_string().into())
1446 .collect(),
1447 ),
1448 );
1449 if let Some(mime_type) = &association.mime_type {
1450 specification.insert("public.mime-type".into(), mime_type.clone().into());
1451 }
1452
1453 dict.insert("UTTypeTagSpecification".into(), specification.into());
1454
1455 Value::Dictionary(dict)
1456 })
1457 })
1458 .collect::<Vec<_>>();
1459
1460 let document_types = associations
1461 .iter()
1462 .map(|association| {
1463 let mut dict = Dictionary::new();
1464
1465 if !association.ext.is_empty() {
1466 dict.insert(
1467 "CFBundleTypeExtensions".into(),
1468 Value::Array(
1469 association
1470 .ext
1471 .iter()
1472 .map(|ext| ext.to_string().into())
1473 .collect(),
1474 ),
1475 );
1476 }
1477
1478 let content_types = association.infer_content_types();
1480
1481 if !content_types.is_empty() {
1483 dict.insert(
1484 "LSItemContentTypes".into(),
1485 Value::Array(content_types.iter().map(|s| s.clone().into()).collect()),
1486 );
1487 }
1488
1489 let type_name = association
1490 .name
1491 .clone()
1492 .or_else(|| association.ext.first().map(|ext| ext.0.clone()))
1493 .unwrap_or_default();
1494 dict.insert("CFBundleTypeName".into(), type_name.into());
1495 dict.insert(
1496 "CFBundleTypeRole".into(),
1497 association.role.to_string().into(),
1498 );
1499 dict.insert("LSHandlerRank".into(), association.rank.to_string().into());
1500
1501 Value::Dictionary(dict)
1502 })
1503 .collect::<Vec<_>>();
1504
1505 if exported_associations.is_empty() && document_types.is_empty() {
1506 return None;
1507 }
1508
1509 let mut plist = Dictionary::new();
1510 if !exported_associations.is_empty() {
1511 plist.insert(
1512 "UTExportedTypeDeclarations".into(),
1513 Value::Array(exported_associations),
1514 );
1515 }
1516 if !document_types.is_empty() {
1517 plist.insert("CFBundleDocumentTypes".into(), Value::Array(document_types));
1518 }
1519
1520 Some(Value::Dictionary(plist))
1521}
1522
1523fn extension_to_uti(ext: &str) -> Option<&'static str> {
1525 match ext.to_lowercase().as_str() {
1526 "png" => Some("public.png"),
1528 "jpg" | "jpeg" => Some("public.jpeg"),
1529 "gif" => Some("com.compuserve.gif"),
1530 "bmp" => Some("com.microsoft.bmp"),
1531 "tiff" | "tif" => Some("public.tiff"),
1532 "ico" => Some("com.microsoft.ico"),
1533 "heic" | "heif" => Some("public.heif-standard-image"),
1534 "webp" => Some("org.webmproject.webp"),
1535 "svg" => Some("public.svg-image"),
1536 "mp4" => Some("public.mpeg-4"),
1538 "mov" => Some("com.apple.quicktime-movie"),
1539 "avi" => Some("public.avi"),
1540 "mkv" => Some("public.mpeg-4"),
1541 "mp3" => Some("public.mp3"),
1543 "wav" => Some("com.microsoft.waveform-audio"),
1544 "aac" => Some("public.aac-audio"),
1545 "m4a" => Some("public.mpeg-4-audio"),
1546 "pdf" => Some("com.adobe.pdf"),
1548 "txt" => Some("public.plain-text"),
1549 "rtf" => Some("public.rtf"),
1550 "html" | "htm" => Some("public.html"),
1551 "json" => Some("public.json"),
1552 "xml" => Some("public.xml"),
1553 _ => None,
1554 }
1555}
1556
1557fn mime_type_to_uti(mime_type: &str) -> Option<&'static str> {
1559 match mime_type {
1560 "image/png" => Some("public.png"),
1561 "image/jpeg" | "image/jpg" => Some("public.jpeg"),
1562 "image/gif" => Some("com.compuserve.gif"),
1563 "image/bmp" => Some("com.microsoft.bmp"),
1564 "image/tiff" => Some("public.tiff"),
1565 "image/heic" | "image/heif" => Some("public.heif-standard-image"),
1566 "image/webp" => Some("org.webmproject.webp"),
1567 "image/svg+xml" => Some("public.svg-image"),
1568 mime if mime.starts_with("image/") => Some("public.image"),
1569 "video/mp4" => Some("public.mpeg-4"),
1570 "video/quicktime" => Some("com.apple.quicktime-movie"),
1571 "video/x-msvideo" => Some("public.avi"),
1572 mime if mime.starts_with("video/") => Some("public.movie"),
1573 "audio/mpeg" | "audio/mp3" => Some("public.mp3"),
1574 "audio/wav" | "audio/wave" => Some("com.microsoft.waveform-audio"),
1575 "audio/aac" => Some("public.aac-audio"),
1576 "audio/mp4" => Some("public.mpeg-4-audio"),
1577 mime if mime.starts_with("audio/") => Some("public.audio"),
1578 "application/pdf" => Some("com.adobe.pdf"),
1579 "text/plain" => Some("public.plain-text"),
1580 "text/rtf" => Some("public.rtf"),
1581 "text/html" => Some("public.html"),
1582 "application/json" => Some("public.json"),
1583 "application/xml" | "text/xml" => Some("public.xml"),
1584 _ => None,
1585 }
1586}
1587
1588#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1590#[cfg_attr(feature = "schema", derive(JsonSchema))]
1591#[serde(rename_all = "camelCase", deny_unknown_fields)]
1592pub struct DeepLinkProtocol {
1593 #[serde(default)]
1595 pub schemes: Vec<String>,
1596 #[serde(default)]
1604 pub domains: Vec<String>,
1605 pub name: Option<String>,
1607 #[serde(default)]
1609 pub role: BundleTypeRole,
1610}
1611
1612#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1615#[cfg_attr(feature = "schema", derive(JsonSchema))]
1616#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
1617pub enum BundleResources {
1618 List(Vec<String>),
1620 Map(HashMap<String, String>),
1622}
1623
1624impl BundleResources {
1625 pub fn push(&mut self, path: impl Into<String>) {
1627 match self {
1628 Self::List(l) => l.push(path.into()),
1629 Self::Map(l) => {
1630 let path = path.into();
1631 l.insert(path.clone(), path);
1632 }
1633 }
1634 }
1635}
1636
1637#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1639#[cfg_attr(feature = "schema", derive(JsonSchema))]
1640#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
1641pub enum Updater {
1642 String(V1Compatible),
1644 Bool(bool),
1647}
1648
1649impl Default for Updater {
1650 fn default() -> Self {
1651 Self::Bool(false)
1652 }
1653}
1654
1655#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1657#[cfg_attr(feature = "schema", derive(JsonSchema))]
1658#[serde(rename_all = "camelCase", deny_unknown_fields)]
1659pub enum V1Compatible {
1660 V1Compatible,
1662}
1663
1664#[skip_serializing_none]
1668#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1669#[cfg_attr(feature = "schema", derive(JsonSchema))]
1670#[serde(rename_all = "camelCase", deny_unknown_fields)]
1671pub struct BundleConfig {
1672 #[serde(default)]
1674 pub active: bool,
1675 #[serde(default)]
1677 pub targets: BundleTarget,
1678 #[serde(default)]
1679 pub create_updater_artifacts: Updater,
1681 pub publisher: Option<String>,
1686 pub homepage: Option<String>,
1691 #[serde(default)]
1693 pub icon: Vec<String>,
1694 pub resources: Option<BundleResources>,
1739 pub copyright: Option<String>,
1741 pub license: Option<String>,
1744 #[serde(alias = "license-file")]
1746 pub license_file: Option<PathBuf>,
1747 pub category: Option<String>,
1752 pub file_associations: Option<Vec<FileAssociation>>,
1754 #[serde(alias = "short-description")]
1756 pub short_description: Option<String>,
1757 #[serde(alias = "long-description")]
1759 pub long_description: Option<String>,
1760 #[serde(default, alias = "use-local-tools-dir")]
1768 pub use_local_tools_dir: bool,
1769 #[serde(alias = "external-bin")]
1781 pub external_bin: Option<Vec<String>>,
1782 #[serde(default)]
1784 pub windows: WindowsConfig,
1785 #[serde(default)]
1787 pub linux: LinuxConfig,
1788 #[serde(rename = "macOS", alias = "macos", default)]
1790 pub macos: MacConfig,
1791 #[serde(rename = "iOS", alias = "ios", default)]
1793 pub ios: IosConfig,
1794 #[serde(default)]
1796 pub android: AndroidConfig,
1797 #[serde(default)]
1799 pub cef: CefConfig,
1800}
1801
1802#[skip_serializing_none]
1807#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1808#[cfg_attr(feature = "schema", derive(JsonSchema))]
1809#[serde(rename_all = "camelCase", deny_unknown_fields)]
1810pub struct CefConfig {
1811 #[serde(default = "default_true")]
1822 pub embed: bool,
1823}
1824
1825impl Default for CefConfig {
1826 fn default() -> Self {
1827 Self { embed: true }
1828 }
1829}
1830
1831#[derive(Debug, PartialEq, Eq, Serialize, Default, Clone, Copy)]
1833#[cfg_attr(feature = "schema", derive(JsonSchema), schemars(with = "InnerColor"))]
1834#[serde(rename_all = "camelCase", deny_unknown_fields)]
1835pub struct Color(pub u8, pub u8, pub u8, pub u8);
1836
1837impl From<Color> for (u8, u8, u8, u8) {
1838 fn from(value: Color) -> Self {
1839 (value.0, value.1, value.2, value.3)
1840 }
1841}
1842
1843impl From<Color> for (u8, u8, u8) {
1844 fn from(value: Color) -> Self {
1845 (value.0, value.1, value.2)
1846 }
1847}
1848
1849impl From<(u8, u8, u8, u8)> for Color {
1850 fn from(value: (u8, u8, u8, u8)) -> Self {
1851 Color(value.0, value.1, value.2, value.3)
1852 }
1853}
1854
1855impl From<(u8, u8, u8)> for Color {
1856 fn from(value: (u8, u8, u8)) -> Self {
1857 Color(value.0, value.1, value.2, 255)
1858 }
1859}
1860
1861impl From<Color> for [u8; 4] {
1862 fn from(value: Color) -> Self {
1863 [value.0, value.1, value.2, value.3]
1864 }
1865}
1866
1867impl From<Color> for [u8; 3] {
1868 fn from(value: Color) -> Self {
1869 [value.0, value.1, value.2]
1870 }
1871}
1872
1873impl From<[u8; 4]> for Color {
1874 fn from(value: [u8; 4]) -> Self {
1875 Color(value[0], value[1], value[2], value[3])
1876 }
1877}
1878
1879impl From<[u8; 3]> for Color {
1880 fn from(value: [u8; 3]) -> Self {
1881 Color(value[0], value[1], value[2], 255)
1882 }
1883}
1884
1885impl FromStr for Color {
1886 type Err = String;
1887 fn from_str(mut color: &str) -> Result<Self, Self::Err> {
1888 color = color.trim().strip_prefix('#').unwrap_or(color);
1889 let color = match color.len() {
1890 3 => color.chars()
1891 .flat_map(|c| std::iter::repeat_n(c, 2))
1892 .chain(std::iter::repeat_n('f', 2))
1893 .collect(),
1894 6 => format!("{color}FF"),
1895 8 => color.to_string(),
1896 _ => return Err("Invalid hex color length, must be either 3, 6 or 8, for example: #fff, #ffffff, or #ffffffff".into()),
1897 };
1898
1899 let r = u8::from_str_radix(&color[0..2], 16).map_err(|e| e.to_string())?;
1900 let g = u8::from_str_radix(&color[2..4], 16).map_err(|e| e.to_string())?;
1901 let b = u8::from_str_radix(&color[4..6], 16).map_err(|e| e.to_string())?;
1902 let a = u8::from_str_radix(&color[6..8], 16).map_err(|e| e.to_string())?;
1903
1904 Ok(Color(r, g, b, a))
1905 }
1906}
1907
1908fn default_alpha() -> u8 {
1909 255
1910}
1911
1912#[derive(Deserialize)]
1913#[cfg_attr(feature = "schema", derive(JsonSchema))]
1914#[serde(untagged)]
1915enum InnerColor {
1916 String(
1918 #[cfg_attr(
1919 feature = "schema",
1920 schemars(pattern("^#?([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$"))
1921 )]
1922 String,
1923 ),
1924 Rgb((u8, u8, u8)),
1926 Rgba((u8, u8, u8, u8)),
1928 RgbaObject {
1930 red: u8,
1931 green: u8,
1932 blue: u8,
1933 #[serde(default = "default_alpha")]
1934 alpha: u8,
1935 },
1936}
1937
1938impl<'de> Deserialize<'de> for Color {
1939 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1940 where
1941 D: Deserializer<'de>,
1942 {
1943 let color = InnerColor::deserialize(deserializer)?;
1944 let color = match color {
1945 InnerColor::String(string) => string.parse().map_err(serde::de::Error::custom)?,
1946 InnerColor::Rgb(rgb) => Color(rgb.0, rgb.1, rgb.2, 255),
1947 InnerColor::Rgba(rgb) => rgb.into(),
1948 InnerColor::RgbaObject {
1949 red,
1950 green,
1951 blue,
1952 alpha,
1953 } => Color(red, green, blue, alpha),
1954 };
1955
1956 Ok(color)
1957 }
1958}
1959
1960#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1962#[cfg_attr(feature = "schema", derive(JsonSchema))]
1963#[serde(rename_all = "camelCase", deny_unknown_fields)]
1964pub enum BackgroundThrottlingPolicy {
1965 Disabled,
1967 Suspend,
1969 Throttle,
1971}
1972
1973#[skip_serializing_none]
1975#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default)]
1976#[cfg_attr(feature = "schema", derive(JsonSchema))]
1977#[serde(rename_all = "camelCase", deny_unknown_fields)]
1978pub struct WindowEffectsConfig {
1979 pub effects: Vec<WindowEffect>,
1985 pub state: Option<WindowEffectState>,
1987 pub radius: Option<f64>,
1989 pub color: Option<Color>,
1997 #[serde(default)]
2001 pub interactive: bool,
2002}
2003
2004#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default)]
2007#[cfg_attr(feature = "schema", derive(JsonSchema))]
2008#[serde(rename_all = "camelCase", deny_unknown_fields)]
2009pub struct PreventOverflowMargin {
2010 pub width: u32,
2012 pub height: u32,
2014}
2015
2016#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
2018#[cfg_attr(feature = "schema", derive(JsonSchema))]
2019#[serde(untagged)]
2020pub enum PreventOverflowConfig {
2021 Enable(bool),
2023 Margin(PreventOverflowMargin),
2026}
2027
2028#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Default)]
2034#[cfg_attr(feature = "schema", derive(JsonSchema))]
2035#[serde(rename_all = "camelCase", deny_unknown_fields)]
2036#[non_exhaustive]
2037pub enum ScrollBarStyle {
2038 #[default]
2039 Default,
2043
2044 FluentOverlay,
2049}
2050
2051#[skip_serializing_none]
2055#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
2056#[cfg_attr(feature = "schema", derive(JsonSchema))]
2057#[serde(rename_all = "camelCase", deny_unknown_fields)]
2058pub struct WindowConfig {
2059 #[serde(default = "default_window_label")]
2061 pub label: String,
2062 #[serde(default = "default_true")]
2077 pub create: bool,
2078 #[serde(default)]
2080 pub url: WebviewUrl,
2081 #[serde(alias = "user-agent")]
2083 pub user_agent: Option<String>,
2084 #[serde(default = "default_true", alias = "drag-drop-enabled")]
2094 pub drag_drop_enabled: bool,
2095 #[serde(default)]
2097 pub center: bool,
2098 pub x: Option<f64>,
2100 pub y: Option<f64>,
2102 #[serde(default = "default_width")]
2104 pub width: f64,
2105 #[serde(default = "default_height")]
2107 pub height: f64,
2108 #[serde(alias = "min-width")]
2110 pub min_width: Option<f64>,
2111 #[serde(alias = "min-height")]
2113 pub min_height: Option<f64>,
2114 #[serde(alias = "max-width")]
2116 pub max_width: Option<f64>,
2117 #[serde(alias = "max-height")]
2119 pub max_height: Option<f64>,
2120 #[serde(alias = "prevent-overflow")]
2126 pub prevent_overflow: Option<PreventOverflowConfig>,
2127 #[serde(default = "default_true")]
2129 pub resizable: bool,
2130 #[serde(default = "default_true")]
2138 pub maximizable: bool,
2139 #[serde(default = "default_true")]
2145 pub minimizable: bool,
2146 #[serde(default = "default_true")]
2154 pub closable: bool,
2155 #[serde(default = "default_title")]
2157 pub title: String,
2158 #[serde(default)]
2160 pub fullscreen: bool,
2161 #[serde(default = "default_true")]
2163 pub focus: bool,
2164 #[serde(default = "default_true")]
2166 pub focusable: bool,
2167 #[serde(default)]
2174 pub transparent: bool,
2175 #[serde(default)]
2177 pub maximized: bool,
2178 #[serde(default = "default_true")]
2180 pub visible: bool,
2181 #[serde(default = "default_true")]
2183 pub decorations: bool,
2184 #[serde(default, alias = "always-on-bottom")]
2186 pub always_on_bottom: bool,
2187 #[serde(default, alias = "always-on-top")]
2189 pub always_on_top: bool,
2190 #[serde(default, alias = "visible-on-all-workspaces")]
2196 pub visible_on_all_workspaces: bool,
2197 #[serde(default, alias = "content-protected")]
2199 pub content_protected: bool,
2200 #[serde(default, alias = "skip-taskbar")]
2202 pub skip_taskbar: bool,
2203 pub window_classname: Option<String>,
2205 #[serde(default, alias = "no-redirection-bitmap")]
2210 pub no_redirection_bitmap: bool,
2211 pub theme: Option<crate::Theme>,
2213 #[serde(default, alias = "title-bar-style")]
2215 pub title_bar_style: TitleBarStyle,
2216 #[serde(default, alias = "traffic-light-position")]
2220 pub traffic_light_position: Option<LogicalPosition>,
2221 #[serde(default, alias = "hidden-title")]
2223 pub hidden_title: bool,
2224 #[serde(default, alias = "accept-first-mouse")]
2232 pub accept_first_mouse: bool,
2233 #[serde(default, alias = "tabbing-identifier")]
2240 pub tabbing_identifier: Option<String>,
2241 #[serde(default, alias = "additional-browser-args")]
2255 pub additional_browser_args: Option<String>,
2256 #[serde(default = "default_true")]
2266 pub shadow: bool,
2267 #[serde(default, alias = "window-effects")]
2276 pub window_effects: Option<WindowEffectsConfig>,
2277 #[serde(default)]
2283 pub incognito: bool,
2284 pub parent: Option<String>,
2296 #[serde(alias = "proxy-url")]
2304 pub proxy_url: Option<Url>,
2305 #[serde(default, alias = "zoom-hotkeys-enabled")]
2315 pub zoom_hotkeys_enabled: bool,
2316 #[serde(default, alias = "browser-extensions-enabled")]
2324 pub browser_extensions_enabled: bool,
2325
2326 #[serde(default, alias = "use-https-scheme")]
2336 pub use_https_scheme: bool,
2337 pub devtools: Option<bool>,
2347
2348 #[serde(alias = "background-color")]
2356 pub background_color: Option<Color>,
2357
2358 #[serde(default, alias = "background-throttling")]
2374 pub background_throttling: Option<BackgroundThrottlingPolicy>,
2375 #[serde(default, alias = "javascript-disabled")]
2377 pub javascript_disabled: bool,
2378 #[serde(default = "default_true", alias = "allow-link-preview")]
2383 pub allow_link_preview: bool,
2384 #[serde(
2389 default,
2390 alias = "disable-input-accessory-view",
2391 alias = "disable_input_accessory_view"
2392 )]
2393 pub disable_input_accessory_view: bool,
2394 #[serde(default, alias = "data-directory")]
2409 pub data_directory: Option<PathBuf>,
2410 #[serde(default, alias = "data-store-identifier")]
2423 pub data_store_identifier: Option<[u8; 16]>,
2424
2425 #[serde(default, alias = "scroll-bar-style")]
2439 pub scroll_bar_style: ScrollBarStyle,
2440
2441 #[serde(default, alias = "limit-navigations-to-app-bound-domains")]
2488 pub limit_navigations_to_app_bound_domains: bool,
2489 #[serde(default, alias = "activity-name")]
2491 pub activity_name: Option<String>,
2492 #[serde(default, alias = "created-by-activity-name")]
2496 pub created_by_activity_name: Option<String>,
2497
2498 #[serde(default, alias = "requested-by-scene-identifier")]
2503 pub requested_by_scene_identifier: Option<String>,
2504 #[serde(default = "default_true", alias = "general-autofill-enabled")]
2523 pub general_autofill_enabled: bool,
2524}
2525
2526impl Default for WindowConfig {
2527 fn default() -> Self {
2528 Self {
2529 label: default_window_label(),
2530 url: WebviewUrl::default(),
2531 create: true,
2532 user_agent: None,
2533 drag_drop_enabled: true,
2534 center: false,
2535 x: None,
2536 y: None,
2537 width: default_width(),
2538 height: default_height(),
2539 min_width: None,
2540 min_height: None,
2541 max_width: None,
2542 max_height: None,
2543 prevent_overflow: None,
2544 resizable: true,
2545 maximizable: true,
2546 minimizable: true,
2547 closable: true,
2548 title: default_title(),
2549 fullscreen: false,
2550 focus: true,
2551 focusable: true,
2552 transparent: false,
2553 maximized: false,
2554 visible: true,
2555 decorations: true,
2556 always_on_bottom: false,
2557 always_on_top: false,
2558 visible_on_all_workspaces: false,
2559 content_protected: false,
2560 skip_taskbar: false,
2561 window_classname: None,
2562 no_redirection_bitmap: false,
2563 theme: None,
2564 title_bar_style: Default::default(),
2565 traffic_light_position: None,
2566 hidden_title: false,
2567 accept_first_mouse: false,
2568 tabbing_identifier: None,
2569 additional_browser_args: None,
2570 shadow: true,
2571 window_effects: None,
2572 incognito: false,
2573 parent: None,
2574 proxy_url: None,
2575 zoom_hotkeys_enabled: false,
2576 browser_extensions_enabled: false,
2577 use_https_scheme: false,
2578 devtools: None,
2579 background_color: None,
2580 background_throttling: None,
2581 javascript_disabled: false,
2582 allow_link_preview: true,
2583 disable_input_accessory_view: false,
2584 data_directory: None,
2585 data_store_identifier: None,
2586 scroll_bar_style: ScrollBarStyle::Default,
2587 limit_navigations_to_app_bound_domains: false,
2588 activity_name: None,
2589 created_by_activity_name: None,
2590 requested_by_scene_identifier: None,
2591 general_autofill_enabled: true,
2592 }
2593 }
2594}
2595
2596fn default_window_label() -> String {
2597 "main".to_string()
2598}
2599
2600fn default_width() -> f64 {
2601 800.
2602}
2603
2604fn default_height() -> f64 {
2605 600.
2606}
2607
2608fn default_title() -> String {
2609 "Tauri App".to_string()
2610}
2611
2612#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2615#[cfg_attr(feature = "schema", derive(JsonSchema))]
2616#[serde(rename_all = "camelCase", untagged)]
2617pub enum CspDirectiveSources {
2618 Inline(String),
2620 List(Vec<String>),
2622}
2623
2624impl Default for CspDirectiveSources {
2625 fn default() -> Self {
2626 Self::List(Vec::new())
2627 }
2628}
2629
2630impl From<CspDirectiveSources> for Vec<String> {
2631 fn from(sources: CspDirectiveSources) -> Self {
2632 match sources {
2633 CspDirectiveSources::Inline(source) => source.split(' ').map(|s| s.to_string()).collect(),
2634 CspDirectiveSources::List(l) => l,
2635 }
2636 }
2637}
2638
2639impl CspDirectiveSources {
2640 pub fn contains(&self, source: &str) -> bool {
2642 match self {
2643 Self::Inline(s) => s.contains(&format!("{source} ")) || s.contains(&format!(" {source}")),
2644 Self::List(l) => l.contains(&source.into()),
2645 }
2646 }
2647
2648 pub fn push<S: AsRef<str>>(&mut self, source: S) {
2650 match self {
2651 Self::Inline(s) => {
2652 s.push(' ');
2653 s.push_str(source.as_ref());
2654 }
2655 Self::List(l) => {
2656 l.push(source.as_ref().to_string());
2657 }
2658 }
2659 }
2660
2661 pub fn extend(&mut self, sources: Vec<String>) {
2663 for s in sources {
2664 self.push(s);
2665 }
2666 }
2667}
2668
2669#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
2672#[cfg_attr(feature = "schema", derive(JsonSchema))]
2673#[serde(rename_all = "camelCase", untagged)]
2674pub enum Csp {
2675 Policy(String),
2677 DirectiveMap(HashMap<String, CspDirectiveSources>),
2679}
2680
2681impl Serialize for Csp {
2682 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
2683 where
2684 S: Serializer,
2685 {
2686 match self {
2687 Self::Policy(policy) => serializer.serialize_str(policy),
2688 Self::DirectiveMap(map) => {
2689 let btree_map: BTreeMap<_, _> = map.iter().collect();
2693 btree_map.serialize(serializer)
2694 }
2695 }
2696 }
2697}
2698
2699impl From<HashMap<String, CspDirectiveSources>> for Csp {
2700 fn from(map: HashMap<String, CspDirectiveSources>) -> Self {
2701 Self::DirectiveMap(map)
2702 }
2703}
2704
2705impl From<Csp> for HashMap<String, CspDirectiveSources> {
2706 fn from(csp: Csp) -> Self {
2707 match csp {
2708 Csp::Policy(policy) => {
2709 let mut map = HashMap::new();
2710 for directive in policy.split(';') {
2711 let mut tokens = directive.trim().split(' ');
2712 if let Some(directive) = tokens.next() {
2713 let sources = tokens.map(|s| s.to_string()).collect::<Vec<String>>();
2714 map.insert(directive.to_string(), CspDirectiveSources::List(sources));
2715 }
2716 }
2717 map
2718 }
2719 Csp::DirectiveMap(m) => m,
2720 }
2721 }
2722}
2723
2724impl Display for Csp {
2725 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2726 match self {
2727 Self::Policy(s) => write!(f, "{s}"),
2728 Self::DirectiveMap(m) => {
2729 let len = m.len();
2730 let mut i = 0;
2731 for (directive, sources) in m {
2732 let sources: Vec<String> = sources.clone().into();
2733 write!(f, "{} {}", directive, sources.join(" "))?;
2734 i += 1;
2735 if i != len {
2736 write!(f, "; ")?;
2737 }
2738 }
2739 Ok(())
2740 }
2741 }
2742 }
2743}
2744
2745#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2747#[serde(untagged)]
2748#[cfg_attr(feature = "schema", derive(JsonSchema))]
2749pub enum DisabledCspModificationKind {
2750 Flag(bool),
2753 List(Vec<String>),
2755}
2756
2757impl DisabledCspModificationKind {
2758 pub fn can_modify(&self, directive: &str) -> bool {
2760 match self {
2761 Self::Flag(f) => !f,
2762 Self::List(l) => !l.contains(&directive.into()),
2763 }
2764 }
2765}
2766
2767impl Default for DisabledCspModificationKind {
2768 fn default() -> Self {
2769 Self::Flag(false)
2770 }
2771}
2772
2773#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2782#[serde(untagged)]
2783#[cfg_attr(feature = "schema", derive(JsonSchema))]
2784pub enum FsScope {
2785 AllowedPaths(Vec<PathBuf>),
2787 #[serde(rename_all = "camelCase")]
2789 Scope {
2790 #[serde(default)]
2792 allow: Vec<PathBuf>,
2793 #[serde(default)]
2796 deny: Vec<PathBuf>,
2797 #[serde(alias = "require-literal-leading-dot")]
2806 require_literal_leading_dot: Option<bool>,
2807 },
2808}
2809
2810impl Default for FsScope {
2811 fn default() -> Self {
2812 Self::AllowedPaths(Vec::new())
2813 }
2814}
2815
2816impl FsScope {
2817 pub fn allowed_paths(&self) -> &Vec<PathBuf> {
2819 match self {
2820 Self::AllowedPaths(p) => p,
2821 Self::Scope { allow, .. } => allow,
2822 }
2823 }
2824
2825 pub fn forbidden_paths(&self) -> Option<&Vec<PathBuf>> {
2827 match self {
2828 Self::AllowedPaths(_) => None,
2829 Self::Scope { deny, .. } => Some(deny),
2830 }
2831 }
2832}
2833
2834#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2838#[cfg_attr(feature = "schema", derive(JsonSchema))]
2839#[serde(rename_all = "camelCase", deny_unknown_fields)]
2840pub struct AssetProtocolConfig {
2841 #[serde(default)]
2843 pub scope: FsScope,
2844 #[serde(default)]
2846 pub enable: bool,
2847}
2848
2849#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
2853#[cfg_attr(feature = "schema", derive(JsonSchema))]
2854#[serde(rename_all = "camelCase", untagged)]
2855pub enum HeaderSource {
2856 Inline(String),
2858 List(Vec<String>),
2860 Map(HashMap<String, String>),
2862}
2863
2864impl Serialize for HeaderSource {
2865 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
2866 where
2867 S: Serializer,
2868 {
2869 match self {
2870 Self::Inline(s) => serializer.serialize_str(s),
2871 Self::List(l) => l.serialize(serializer),
2872 Self::Map(m) => {
2873 let btree_map: BTreeMap<_, _> = m.iter().collect();
2877 btree_map.serialize(serializer)
2878 }
2879 }
2880 }
2881}
2882
2883impl Display for HeaderSource {
2884 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2885 match self {
2886 Self::Inline(s) => write!(f, "{s}"),
2887 Self::List(l) => write!(f, "{}", l.join(", ")),
2888 Self::Map(m) => {
2889 let map: BTreeMap<_, _> = m.iter().collect();
2893 let len = map.len();
2894 for (i, (key, value)) in map.into_iter().enumerate() {
2895 write!(f, "{key} {value}")?;
2896 if i + 1 != len {
2897 write!(f, "; ")?;
2898 }
2899 }
2900 Ok(())
2901 }
2902 }
2903 }
2904}
2905
2906pub trait HeaderAddition {
2910 fn add_configured_headers(self, headers: Option<&HeaderConfig>) -> http::response::Builder;
2912}
2913
2914impl HeaderAddition for http::response::Builder {
2915 fn add_configured_headers(mut self, headers: Option<&HeaderConfig>) -> http::response::Builder {
2919 if let Some(headers) = headers {
2920 if let Some(value) = &headers.access_control_allow_credentials {
2922 self = self.header("Access-Control-Allow-Credentials", value.to_string());
2923 };
2924
2925 if let Some(value) = &headers.access_control_allow_headers {
2927 self = self.header("Access-Control-Allow-Headers", value.to_string());
2928 };
2929
2930 if let Some(value) = &headers.access_control_allow_methods {
2932 self = self.header("Access-Control-Allow-Methods", value.to_string());
2933 };
2934
2935 if let Some(value) = &headers.access_control_expose_headers {
2937 self = self.header("Access-Control-Expose-Headers", value.to_string());
2938 };
2939
2940 if let Some(value) = &headers.access_control_max_age {
2942 self = self.header("Access-Control-Max-Age", value.to_string());
2943 };
2944
2945 if let Some(value) = &headers.cross_origin_embedder_policy {
2947 self = self.header("Cross-Origin-Embedder-Policy", value.to_string());
2948 };
2949
2950 if let Some(value) = &headers.cross_origin_opener_policy {
2952 self = self.header("Cross-Origin-Opener-Policy", value.to_string());
2953 };
2954
2955 if let Some(value) = &headers.cross_origin_resource_policy {
2957 self = self.header("Cross-Origin-Resource-Policy", value.to_string());
2958 };
2959
2960 if let Some(value) = &headers.permissions_policy {
2962 self = self.header("Permissions-Policy", value.to_string());
2963 };
2964
2965 if let Some(value) = &headers.service_worker_allowed {
2966 self = self.header("Service-Worker-Allowed", value.to_string());
2967 }
2968
2969 if let Some(value) = &headers.timing_allow_origin {
2971 self = self.header("Timing-Allow-Origin", value.to_string());
2972 };
2973
2974 if let Some(value) = &headers.x_content_type_options {
2976 self = self.header("X-Content-Type-Options", value.to_string());
2977 };
2978
2979 if let Some(value) = &headers.tauri_custom_header {
2981 self = self.header("Tauri-Custom-Header", value.to_string());
2983 };
2984 }
2985 self
2986 }
2987}
2988
2989#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
3041#[cfg_attr(feature = "schema", derive(JsonSchema))]
3042#[serde(deny_unknown_fields)]
3043pub struct HeaderConfig {
3044 #[serde(rename = "Access-Control-Allow-Credentials")]
3049 pub access_control_allow_credentials: Option<HeaderSource>,
3050 #[serde(rename = "Access-Control-Allow-Headers")]
3058 pub access_control_allow_headers: Option<HeaderSource>,
3059 #[serde(rename = "Access-Control-Allow-Methods")]
3064 pub access_control_allow_methods: Option<HeaderSource>,
3065 #[serde(rename = "Access-Control-Expose-Headers")]
3071 pub access_control_expose_headers: Option<HeaderSource>,
3072 #[serde(rename = "Access-Control-Max-Age")]
3079 pub access_control_max_age: Option<HeaderSource>,
3080 #[serde(rename = "Cross-Origin-Embedder-Policy")]
3085 pub cross_origin_embedder_policy: Option<HeaderSource>,
3086 #[serde(rename = "Cross-Origin-Opener-Policy")]
3093 pub cross_origin_opener_policy: Option<HeaderSource>,
3094 #[serde(rename = "Cross-Origin-Resource-Policy")]
3099 pub cross_origin_resource_policy: Option<HeaderSource>,
3100 #[serde(rename = "Permissions-Policy")]
3105 pub permissions_policy: Option<HeaderSource>,
3106 #[serde(rename = "Service-Worker-Allowed")]
3116 pub service_worker_allowed: Option<HeaderSource>,
3117 #[serde(rename = "Timing-Allow-Origin")]
3123 pub timing_allow_origin: Option<HeaderSource>,
3124 #[serde(rename = "X-Content-Type-Options")]
3131 pub x_content_type_options: Option<HeaderSource>,
3132 #[serde(rename = "Tauri-Custom-Header")]
3137 pub tauri_custom_header: Option<HeaderSource>,
3138}
3139
3140impl HeaderConfig {
3141 pub fn new() -> Self {
3143 HeaderConfig {
3144 access_control_allow_credentials: None,
3145 access_control_allow_methods: None,
3146 access_control_allow_headers: None,
3147 access_control_expose_headers: None,
3148 access_control_max_age: None,
3149 cross_origin_embedder_policy: None,
3150 cross_origin_opener_policy: None,
3151 cross_origin_resource_policy: None,
3152 permissions_policy: None,
3153 service_worker_allowed: None,
3154 timing_allow_origin: None,
3155 x_content_type_options: None,
3156 tauri_custom_header: None,
3157 }
3158 }
3159}
3160
3161#[skip_serializing_none]
3165#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
3166#[cfg_attr(feature = "schema", derive(JsonSchema))]
3167#[serde(rename_all = "camelCase", deny_unknown_fields)]
3168pub struct SecurityConfig {
3169 pub csp: Option<Csp>,
3175 #[serde(alias = "dev-csp")]
3180 pub dev_csp: Option<Csp>,
3181 #[serde(default, alias = "freeze-prototype")]
3194 pub freeze_prototype: bool,
3195 #[serde(default, alias = "dangerous-disable-asset-csp-modification")]
3208 pub dangerous_disable_asset_csp_modification: DisabledCspModificationKind,
3209 #[serde(default, alias = "asset-protocol")]
3211 pub asset_protocol: AssetProtocolConfig,
3212 #[serde(default)]
3224 pub pattern: PatternKind,
3225 #[serde(default)]
3250 pub capabilities: Vec<CapabilityEntry>,
3251 #[serde(default)]
3254 pub headers: Option<HeaderConfig>,
3255}
3256
3257#[derive(Debug, Clone, PartialEq, Serialize)]
3259#[cfg_attr(feature = "schema", derive(JsonSchema))]
3260#[serde(untagged)]
3261pub enum CapabilityEntry {
3262 Inlined(Capability),
3264 Reference(String),
3266}
3267
3268impl<'de> Deserialize<'de> for CapabilityEntry {
3269 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3270 where
3271 D: Deserializer<'de>,
3272 {
3273 UntaggedEnumVisitor::new()
3274 .string(|string| Ok(Self::Reference(string.to_owned())))
3275 .map(|map| map.deserialize::<Capability>().map(Self::Inlined))
3276 .deserialize(deserializer)
3277 }
3278}
3279
3280#[skip_serializing_none]
3282#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default)]
3283#[serde(rename_all = "lowercase", tag = "use", content = "options")]
3284#[cfg_attr(feature = "schema", derive(JsonSchema))]
3285pub enum PatternKind {
3286 #[default]
3288 Brownfield,
3289 Isolation {
3291 dir: PathBuf,
3293 },
3294}
3295
3296const APP_DIRECTORIES_OVERRIDE_VARIABLES: &[&str] = &[
3302 "$AUDIO",
3303 "$CACHE",
3304 "$CONFIG",
3305 "$DATA",
3306 "$LOCALDATA",
3307 "$DESKTOP",
3308 "$DOCUMENT",
3309 "$DOWNLOAD",
3310 "$HOME",
3311 "$PICTURE",
3312 "$PUBLIC",
3313 "$TEMP",
3314 "$VIDEO",
3315];
3316
3317fn validate_app_directory_override(path: &Path) -> Result<(), String> {
3319 let mut components = path.components();
3320 let first = components.next();
3321
3322 if let Some(Component::Normal(first)) = first {
3323 if let Some(variable) = first.to_str().filter(|s| s.starts_with('$')) {
3324 if !APP_DIRECTORIES_OVERRIDE_VARIABLES.contains(&variable) {
3325 return Err(format!(
3326 "`{}` starts with the unsupported base directory variable `{variable}`, expected one of {}",
3327 path.display(),
3328 APP_DIRECTORIES_OVERRIDE_VARIABLES
3329 .iter()
3330 .map(|v| format!("`{v}`"))
3331 .collect::<Vec<_>>()
3332 .join(", ")
3333 ));
3334 }
3335 return Ok(());
3336 }
3337 }
3338
3339 if !path.is_absolute() && (path.has_root() || matches!(first, Some(Component::Prefix(_)))) {
3342 return Err(format!(
3343 "`{}` must be an absolute path, a path relative to the executable or a path starting with a base directory variable",
3344 path.display()
3345 ));
3346 }
3347
3348 Ok(())
3349}
3350
3351#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
3355#[cfg_attr(feature = "schema", derive(JsonSchema))]
3356#[serde(untagged)]
3357pub enum AppDirectoriesOverride {
3358 Root(PathBuf),
3363 Directories(AppDirectoryOverrides),
3367}
3368
3369impl AppDirectoriesOverride {
3370 fn paths(&self) -> impl Iterator<Item = &PathBuf> {
3372 match self {
3373 Self::Root(root) => vec![Some(root)],
3374 Self::Directories(directories) => vec![
3375 directories.config.as_ref(),
3376 directories.data.as_ref(),
3377 directories.local_data.as_ref(),
3378 directories.cache.as_ref(),
3379 directories.log.as_ref(),
3380 ],
3381 }
3382 .into_iter()
3383 .flatten()
3384 }
3385}
3386
3387impl<'de> Deserialize<'de> for AppDirectoriesOverride {
3388 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3389 where
3390 D: Deserializer<'de>,
3391 {
3392 let value = UntaggedEnumVisitor::new()
3393 .string(|path| Ok(Self::Root(PathBuf::from(path))))
3394 .map(|map| {
3395 map
3396 .deserialize::<AppDirectoryOverrides>()
3397 .map(Self::Directories)
3398 })
3399 .deserialize(deserializer)?;
3400
3401 for path in value.paths() {
3402 validate_app_directory_override(path).map_err(DeError::custom)?;
3403 }
3404
3405 Ok(value)
3406 }
3407}
3408
3409#[skip_serializing_none]
3411#[derive(Debug, Default, PartialEq, Eq, Clone, Serialize, Deserialize)]
3412#[cfg_attr(feature = "schema", derive(JsonSchema))]
3413#[serde(rename_all = "camelCase", deny_unknown_fields)]
3414pub struct AppDirectoryOverrides {
3415 pub config: Option<PathBuf>,
3417 pub data: Option<PathBuf>,
3419 #[serde(alias = "local-data", alias = "local_data")]
3423 pub local_data: Option<PathBuf>,
3424 pub cache: Option<PathBuf>,
3426 pub log: Option<PathBuf>,
3428}
3429
3430#[skip_serializing_none]
3434#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
3435#[cfg_attr(feature = "schema", derive(JsonSchema))]
3436#[serde(rename_all = "camelCase", deny_unknown_fields)]
3437pub struct AppConfig {
3438 #[serde(default)]
3493 pub windows: Vec<WindowConfig>,
3494 #[serde(default)]
3496 pub security: SecurityConfig,
3497 #[serde(alias = "tray-icon")]
3499 pub tray_icon: Option<TrayIconConfig>,
3500 #[serde(default, alias = "with-global-tauri")]
3502 pub with_global_tauri: bool,
3503 #[serde(rename = "enableGTKAppId", alias = "enable-gtk-app-id", default)]
3519 pub enable_gtk_app_id: bool,
3520 #[serde(alias = "app-directories-override")]
3632 pub app_directories_override: Option<AppDirectoriesOverride>,
3633}
3634
3635impl AppConfig {
3636 pub fn all_features() -> Vec<&'static str> {
3638 vec!["tray-icon", "protocol-asset", "isolation"]
3639 }
3640
3641 pub fn features(&self) -> Vec<&str> {
3643 let mut features = Vec::new();
3644 if self.tray_icon.is_some() {
3645 features.push("tray-icon");
3646 }
3647 if self.security.asset_protocol.enable {
3648 features.push("protocol-asset");
3649 }
3650
3651 if let PatternKind::Isolation { .. } = self.security.pattern {
3652 features.push("isolation");
3653 }
3654
3655 features.sort_unstable();
3656 features
3657 }
3658}
3659
3660#[skip_serializing_none]
3664#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
3665#[cfg_attr(feature = "schema", derive(JsonSchema))]
3666#[serde(rename_all = "camelCase", deny_unknown_fields)]
3667pub struct TrayIconConfig {
3668 pub id: Option<String>,
3670 #[serde(alias = "icon-path")]
3676 pub icon_path: PathBuf,
3677 #[serde(default, alias = "icon-as-template")]
3679 pub icon_as_template: bool,
3680 #[serde(default = "default_true", alias = "menu-on-left-click")]
3688 #[deprecated(
3689 since = "2.2.0",
3690 note = "No longer works, use `show_menu_on_left_click` instead."
3691 )]
3692 pub menu_on_left_click: bool,
3693 #[serde(default = "default_true", alias = "show-menu-on-left-click")]
3699 pub show_menu_on_left_click: bool,
3700 pub title: Option<String>,
3702 pub tooltip: Option<String>,
3704}
3705
3706#[skip_serializing_none]
3708#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
3709#[cfg_attr(feature = "schema", derive(JsonSchema))]
3710#[serde(rename_all = "camelCase", deny_unknown_fields)]
3711pub struct IosConfig {
3712 pub template: Option<PathBuf>,
3716 pub frameworks: Option<Vec<String>>,
3720 #[serde(alias = "development-team")]
3723 pub development_team: Option<String>,
3724 #[serde(alias = "bundle-version")]
3728 pub bundle_version: Option<String>,
3729 #[serde(
3733 alias = "minimum-system-version",
3734 default = "ios_minimum_system_version"
3735 )]
3736 pub minimum_system_version: String,
3737 #[serde(alias = "info-plist")]
3741 pub info_plist: Option<PathBuf>,
3742}
3743
3744impl Default for IosConfig {
3745 fn default() -> Self {
3746 Self {
3747 template: None,
3748 frameworks: None,
3749 development_team: None,
3750 bundle_version: None,
3751 minimum_system_version: ios_minimum_system_version(),
3752 info_plist: None,
3753 }
3754 }
3755}
3756
3757#[skip_serializing_none]
3759#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
3760#[cfg_attr(feature = "schema", derive(JsonSchema))]
3761#[serde(rename_all = "camelCase", deny_unknown_fields)]
3762pub struct AndroidConfig {
3763 #[serde(alias = "min-sdk-version", default = "default_min_sdk_version")]
3766 pub min_sdk_version: u32,
3767
3768 #[serde(alias = "version-code")]
3774 #[cfg_attr(feature = "schema", validate(range(min = 1, max = 2_100_000_000)))]
3775 pub version_code: Option<u32>,
3776
3777 #[serde(alias = "auto-increment-version-code", default)]
3785 pub auto_increment_version_code: bool,
3786
3787 #[serde(alias = "debug-application-id-suffix")]
3791 pub debug_application_id_suffix: Option<String>,
3792}
3793
3794impl Default for AndroidConfig {
3795 fn default() -> Self {
3796 Self {
3797 min_sdk_version: default_min_sdk_version(),
3798 version_code: None,
3799 auto_increment_version_code: false,
3800 debug_application_id_suffix: None,
3801 }
3802 }
3803}
3804
3805fn default_min_sdk_version() -> u32 {
3806 24
3807}
3808
3809#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
3811#[cfg_attr(feature = "schema", derive(JsonSchema))]
3812#[serde(untagged, deny_unknown_fields)]
3813#[non_exhaustive]
3814pub enum FrontendDist {
3815 Url(Url),
3817 Directory(PathBuf),
3819 Files(Vec<PathBuf>),
3821}
3822
3823impl std::fmt::Display for FrontendDist {
3824 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3825 match self {
3826 Self::Url(url) => write!(f, "{url}"),
3827 Self::Directory(p) => write!(f, "{}", p.display()),
3828 Self::Files(files) => write!(f, "{}", serde_json::to_string(files).unwrap()),
3829 }
3830 }
3831}
3832
3833#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
3835#[cfg_attr(feature = "schema", derive(JsonSchema))]
3836#[serde(rename_all = "camelCase", untagged)]
3837pub enum BeforeDevCommand {
3838 Script(String),
3840 ScriptWithOptions {
3842 script: String,
3844 cwd: Option<String>,
3846 #[serde(default)]
3848 wait: bool,
3849 },
3850}
3851
3852#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
3854#[cfg_attr(feature = "schema", derive(JsonSchema))]
3855#[serde(rename_all = "camelCase", untagged)]
3856pub enum HookCommand {
3857 Script(String),
3859 ScriptWithOptions {
3861 script: String,
3863 cwd: Option<String>,
3865 },
3866}
3867
3868#[skip_serializing_none]
3870#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
3871#[cfg_attr(feature = "schema", derive(JsonSchema))]
3872#[serde(untagged)]
3873pub enum RunnerConfig {
3874 String(String),
3876 Object {
3878 cmd: String,
3880 cwd: Option<String>,
3882 args: Option<Vec<String>>,
3884 },
3885}
3886
3887impl Default for RunnerConfig {
3888 fn default() -> Self {
3889 RunnerConfig::String("cargo".to_string())
3890 }
3891}
3892
3893impl RunnerConfig {
3894 pub fn cmd(&self) -> &str {
3896 match self {
3897 RunnerConfig::String(cmd) => cmd,
3898 RunnerConfig::Object { cmd, .. } => cmd,
3899 }
3900 }
3901
3902 pub fn cwd(&self) -> Option<&str> {
3904 match self {
3905 RunnerConfig::String(_) => None,
3906 RunnerConfig::Object { cwd, .. } => cwd.as_deref(),
3907 }
3908 }
3909
3910 pub fn args(&self) -> Option<&[String]> {
3912 match self {
3913 RunnerConfig::String(_) => None,
3914 RunnerConfig::Object { args, .. } => args.as_deref(),
3915 }
3916 }
3917}
3918
3919impl std::str::FromStr for RunnerConfig {
3920 type Err = std::convert::Infallible;
3921
3922 fn from_str(s: &str) -> Result<Self, Self::Err> {
3923 Ok(RunnerConfig::String(s.to_string()))
3924 }
3925}
3926
3927impl From<&str> for RunnerConfig {
3928 fn from(s: &str) -> Self {
3929 RunnerConfig::String(s.to_string())
3930 }
3931}
3932
3933impl From<String> for RunnerConfig {
3934 fn from(s: String) -> Self {
3935 RunnerConfig::String(s)
3936 }
3937}
3938
3939#[skip_serializing_none]
3943#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Default)]
3944#[cfg_attr(feature = "schema", derive(JsonSchema))]
3945#[serde(rename_all = "camelCase", deny_unknown_fields)]
3946pub struct BuildConfig {
3947 pub runner: Option<RunnerConfig>,
3949 #[serde(alias = "dev-url")]
3957 pub dev_url: Option<Url>,
3958 #[serde(alias = "frontend-dist")]
3972 pub frontend_dist: Option<FrontendDist>,
3973 #[serde(alias = "before-dev-command")]
3980 pub before_dev_command: Option<BeforeDevCommand>,
3981 #[serde(alias = "before-build-command")]
3988 pub before_build_command: Option<HookCommand>,
3989 #[serde(alias = "before-bundle-command")]
3996 pub before_bundle_command: Option<HookCommand>,
3997 pub features: Option<Vec<String>>,
3999 #[serde(alias = "remove-unused-commands", default)]
4007 pub remove_unused_commands: bool,
4008 #[serde(
4010 alias = "additional-watch-folders",
4011 alias = "additional-watch-directories",
4012 default
4013 )]
4014 pub additional_watch_folders: Vec<PathBuf>,
4015 #[serde(default)]
4017 pub windows: WindowsBuildConfig,
4018}
4019
4020#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
4022#[cfg_attr(feature = "schema", derive(JsonSchema))]
4023#[serde(rename_all = "camelCase", deny_unknown_fields)]
4024pub struct WindowsBuildConfig {
4025 #[serde(
4027 default = "default_true",
4028 rename = "staticVCRuntime",
4029 alias = "static-vc-runtime",
4030 alias = "staticVcRuntime"
4031 )]
4032 pub static_vc_runtime: bool,
4033}
4034
4035impl Default for WindowsBuildConfig {
4036 fn default() -> Self {
4037 Self {
4038 static_vc_runtime: true,
4039 }
4040 }
4041}
4042
4043#[derive(Debug, PartialEq, Eq)]
4044struct PackageVersion(String);
4045
4046impl<'d> serde::Deserialize<'d> for PackageVersion {
4047 fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {
4048 struct PackageVersionVisitor;
4049
4050 impl Visitor<'_> for PackageVersionVisitor {
4051 type Value = PackageVersion;
4052
4053 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
4054 write!(
4055 formatter,
4056 "a semver string or a path to a package.json file"
4057 )
4058 }
4059
4060 fn visit_str<E: DeError>(self, value: &str) -> Result<PackageVersion, E> {
4061 let path = PathBuf::from(value);
4062 if path.exists() {
4063 let json_str = read_to_string(&path)
4064 .map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?;
4065 let package_json: serde_json::Value = serde_json::from_str(&json_str)
4066 .map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?;
4067 if let Some(obj) = package_json.as_object() {
4068 let version = obj
4069 .get("version")
4070 .ok_or_else(|| DeError::custom("JSON must contain a `version` field"))?
4071 .as_str()
4072 .ok_or_else(|| {
4073 DeError::custom(format!("`{} > version` must be a string", path.display()))
4074 })?;
4075 Ok(PackageVersion(
4076 Version::from_str(version)
4077 .map_err(|_| {
4078 DeError::custom("`tauri.conf.json > version` must be a semver string")
4079 })?
4080 .to_string(),
4081 ))
4082 } else {
4083 Err(DeError::custom(
4084 "`tauri.conf.json > version` value is not a path to a JSON object",
4085 ))
4086 }
4087 } else {
4088 Ok(PackageVersion(
4089 Version::from_str(value)
4090 .map_err(|_| DeError::custom("`tauri.conf.json > version` must be a semver string"))?
4091 .to_string(),
4092 ))
4093 }
4094 }
4095 }
4096
4097 deserializer.deserialize_string(PackageVersionVisitor {})
4098 }
4099}
4100
4101fn version_deserializer<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
4102where
4103 D: Deserializer<'de>,
4104{
4105 Option::<PackageVersion>::deserialize(deserializer).map(|v| v.map(|v| v.0))
4106}
4107
4108#[skip_serializing_none]
4174#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
4175#[cfg_attr(feature = "schema", derive(JsonSchema))]
4176#[serde(rename_all = "camelCase", deny_unknown_fields)]
4177pub struct Config {
4178 #[serde(rename = "$schema")]
4180 pub schema: Option<String>,
4181 #[serde(alias = "product-name")]
4199 #[cfg_attr(feature = "schema", schemars(regex(pattern = "^[^/\\:*?\"<>|]+$")))]
4200 pub product_name: Option<String>,
4201 #[serde(alias = "main-binary-name")]
4215 pub main_binary_name: Option<String>,
4216 #[serde(deserialize_with = "version_deserializer", default)]
4232 pub version: Option<String>,
4233 pub identifier: String,
4241 #[serde(default)]
4243 pub app: AppConfig,
4244 #[serde(default)]
4246 pub build: BuildConfig,
4247 #[serde(default)]
4249 pub bundle: BundleConfig,
4250 #[serde(default)]
4252 pub plugins: PluginConfig,
4253}
4254
4255#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize)]
4259#[cfg_attr(feature = "schema", derive(JsonSchema))]
4260pub struct PluginConfig(pub HashMap<String, JsonValue>);
4261
4262impl Serialize for PluginConfig {
4263 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
4264 where
4265 S: Serializer,
4266 {
4267 let btree_map: BTreeMap<_, _> = self.0.iter().collect();
4271 btree_map.serialize(serializer)
4272 }
4273}
4274
4275#[cfg(any(feature = "build", feature = "build-2"))]
4281mod build {
4282 use super::*;
4283 use crate::{literal_struct, tokens::*};
4284 use proc_macro2::TokenStream;
4285 use quote::{ToTokens, TokenStreamExt, quote};
4286 use std::convert::identity;
4287
4288 impl ToTokens for WebviewUrl {
4289 fn to_tokens(&self, tokens: &mut TokenStream) {
4290 let prefix = quote! { ::tauri::utils::config::WebviewUrl };
4291
4292 tokens.append_all(match self {
4293 Self::App(path) => {
4294 let path = path_buf_lit(path);
4295 quote! { #prefix::App(#path) }
4296 }
4297 Self::External(url) => {
4298 let url = url_lit(url);
4299 quote! { #prefix::External(#url) }
4300 }
4301 Self::CustomProtocol(url) => {
4302 let url = url_lit(url);
4303 quote! { #prefix::CustomProtocol(#url) }
4304 }
4305 })
4306 }
4307 }
4308
4309 impl ToTokens for BackgroundThrottlingPolicy {
4310 fn to_tokens(&self, tokens: &mut TokenStream) {
4311 let prefix = quote! { ::tauri::utils::config::BackgroundThrottlingPolicy };
4312 tokens.append_all(match self {
4313 Self::Disabled => quote! { #prefix::Disabled },
4314 Self::Throttle => quote! { #prefix::Throttle },
4315 Self::Suspend => quote! { #prefix::Suspend },
4316 })
4317 }
4318 }
4319
4320 impl ToTokens for crate::Theme {
4321 fn to_tokens(&self, tokens: &mut TokenStream) {
4322 let prefix = quote! { ::tauri::utils::Theme };
4323
4324 tokens.append_all(match self {
4325 Self::Light => quote! { #prefix::Light },
4326 Self::Dark => quote! { #prefix::Dark },
4327 })
4328 }
4329 }
4330
4331 impl ToTokens for Color {
4332 fn to_tokens(&self, tokens: &mut TokenStream) {
4333 let Color(r, g, b, a) = self;
4334 tokens.append_all(quote! {::tauri::utils::config::Color(#r,#g,#b,#a)});
4335 }
4336 }
4337 impl ToTokens for WindowEffectsConfig {
4338 fn to_tokens(&self, tokens: &mut TokenStream) {
4339 let effects = vec_lit(self.effects.clone(), |d| d);
4340 let state = opt_lit(self.state.as_ref());
4341 let radius = opt_lit(self.radius.as_ref());
4342 let color = opt_lit(self.color.as_ref());
4343 let interactive = self.interactive;
4344
4345 literal_struct!(
4346 tokens,
4347 ::tauri::utils::config::WindowEffectsConfig,
4348 effects,
4349 state,
4350 radius,
4351 color,
4352 interactive
4353 )
4354 }
4355 }
4356
4357 impl ToTokens for crate::TitleBarStyle {
4358 fn to_tokens(&self, tokens: &mut TokenStream) {
4359 let prefix = quote! { ::tauri::utils::TitleBarStyle };
4360
4361 tokens.append_all(match self {
4362 Self::Visible => quote! { #prefix::Visible },
4363 Self::Transparent => quote! { #prefix::Transparent },
4364 Self::Overlay => quote! { #prefix::Overlay },
4365 })
4366 }
4367 }
4368
4369 impl ToTokens for LogicalPosition {
4370 fn to_tokens(&self, tokens: &mut TokenStream) {
4371 let LogicalPosition { x, y } = self;
4372 literal_struct!(tokens, ::tauri::utils::config::LogicalPosition, x, y)
4373 }
4374 }
4375
4376 impl ToTokens for crate::WindowEffect {
4377 fn to_tokens(&self, tokens: &mut TokenStream) {
4378 let prefix = quote! { ::tauri::utils::WindowEffect };
4379
4380 #[allow(deprecated)]
4381 tokens.append_all(match self {
4382 WindowEffect::AppearanceBased => quote! { #prefix::AppearanceBased},
4383 WindowEffect::Light => quote! { #prefix::Light},
4384 WindowEffect::Dark => quote! { #prefix::Dark},
4385 WindowEffect::MediumLight => quote! { #prefix::MediumLight},
4386 WindowEffect::UltraDark => quote! { #prefix::UltraDark},
4387 WindowEffect::Titlebar => quote! { #prefix::Titlebar},
4388 WindowEffect::Selection => quote! { #prefix::Selection},
4389 WindowEffect::Menu => quote! { #prefix::Menu},
4390 WindowEffect::Popover => quote! { #prefix::Popover},
4391 WindowEffect::Sidebar => quote! { #prefix::Sidebar},
4392 WindowEffect::HeaderView => quote! { #prefix::HeaderView},
4393 WindowEffect::Sheet => quote! { #prefix::Sheet},
4394 WindowEffect::WindowBackground => quote! { #prefix::WindowBackground},
4395 WindowEffect::HudWindow => quote! { #prefix::HudWindow},
4396 WindowEffect::FullScreenUI => quote! { #prefix::FullScreenUI},
4397 WindowEffect::Tooltip => quote! { #prefix::Tooltip},
4398 WindowEffect::ContentBackground => quote! { #prefix::ContentBackground},
4399 WindowEffect::UnderWindowBackground => quote! { #prefix::UnderWindowBackground},
4400 WindowEffect::UnderPageBackground => quote! { #prefix::UnderPageBackground},
4401 WindowEffect::LiquidGlassRegular => quote! { #prefix::LiquidGlassRegular },
4402 WindowEffect::LiquidGlassClear => quote! { #prefix::LiquidGlassClear },
4403 WindowEffect::Mica => quote! { #prefix::Mica},
4404 WindowEffect::MicaDark => quote! { #prefix::MicaDark},
4405 WindowEffect::MicaLight => quote! { #prefix::MicaLight},
4406 WindowEffect::Blur => quote! { #prefix::Blur},
4407 WindowEffect::Acrylic => quote! { #prefix::Acrylic},
4408 WindowEffect::Tabbed => quote! { #prefix::Tabbed },
4409 WindowEffect::TabbedDark => quote! { #prefix::TabbedDark },
4410 WindowEffect::TabbedLight => quote! { #prefix::TabbedLight },
4411 })
4412 }
4413 }
4414
4415 impl ToTokens for crate::WindowEffectState {
4416 fn to_tokens(&self, tokens: &mut TokenStream) {
4417 let prefix = quote! { ::tauri::utils::WindowEffectState };
4418
4419 #[allow(deprecated)]
4420 tokens.append_all(match self {
4421 WindowEffectState::Active => quote! { #prefix::Active},
4422 WindowEffectState::FollowsWindowActiveState => quote! { #prefix::FollowsWindowActiveState},
4423 WindowEffectState::Inactive => quote! { #prefix::Inactive},
4424 })
4425 }
4426 }
4427
4428 impl ToTokens for PreventOverflowMargin {
4429 fn to_tokens(&self, tokens: &mut TokenStream) {
4430 let width = self.width;
4431 let height = self.height;
4432
4433 literal_struct!(
4434 tokens,
4435 ::tauri::utils::config::PreventOverflowMargin,
4436 width,
4437 height
4438 )
4439 }
4440 }
4441
4442 impl ToTokens for PreventOverflowConfig {
4443 fn to_tokens(&self, tokens: &mut TokenStream) {
4444 let prefix = quote! { ::tauri::utils::config::PreventOverflowConfig };
4445
4446 #[allow(deprecated)]
4447 tokens.append_all(match self {
4448 Self::Enable(enable) => quote! { #prefix::Enable(#enable) },
4449 Self::Margin(margin) => quote! { #prefix::Margin(#margin) },
4450 })
4451 }
4452 }
4453
4454 impl ToTokens for ScrollBarStyle {
4455 fn to_tokens(&self, tokens: &mut TokenStream) {
4456 let prefix = quote! { ::tauri::utils::config::ScrollBarStyle };
4457
4458 tokens.append_all(match self {
4459 Self::Default => quote! { #prefix::Default },
4460 Self::FluentOverlay => quote! { #prefix::FluentOverlay },
4461 })
4462 }
4463 }
4464
4465 impl ToTokens for WindowConfig {
4466 fn to_tokens(&self, tokens: &mut TokenStream) {
4467 let label = str_lit(&self.label);
4468 let create = &self.create;
4469 let url = &self.url;
4470 let user_agent = opt_str_lit(self.user_agent.as_ref());
4471 let drag_drop_enabled = self.drag_drop_enabled;
4472 let center = self.center;
4473 let x = opt_lit(self.x.as_ref());
4474 let y = opt_lit(self.y.as_ref());
4475 let width = self.width;
4476 let height = self.height;
4477 let min_width = opt_lit(self.min_width.as_ref());
4478 let min_height = opt_lit(self.min_height.as_ref());
4479 let max_width = opt_lit(self.max_width.as_ref());
4480 let max_height = opt_lit(self.max_height.as_ref());
4481 let prevent_overflow = opt_lit(self.prevent_overflow.as_ref());
4482 let resizable = self.resizable;
4483 let maximizable = self.maximizable;
4484 let minimizable = self.minimizable;
4485 let closable = self.closable;
4486 let title = str_lit(&self.title);
4487 let proxy_url = opt_lit(self.proxy_url.as_ref().map(url_lit).as_ref());
4488 let fullscreen = self.fullscreen;
4489 let focus = self.focus;
4490 let focusable = self.focusable;
4491 let transparent = self.transparent;
4492 let maximized = self.maximized;
4493 let visible = self.visible;
4494 let decorations = self.decorations;
4495 let always_on_bottom = self.always_on_bottom;
4496 let always_on_top = self.always_on_top;
4497 let visible_on_all_workspaces = self.visible_on_all_workspaces;
4498 let content_protected = self.content_protected;
4499 let skip_taskbar = self.skip_taskbar;
4500 let window_classname = opt_str_lit(self.window_classname.as_ref());
4501 let no_redirection_bitmap = self.no_redirection_bitmap;
4502 let theme = opt_lit(self.theme.as_ref());
4503 let title_bar_style = &self.title_bar_style;
4504 let traffic_light_position = opt_lit(self.traffic_light_position.as_ref());
4505 let hidden_title = self.hidden_title;
4506 let accept_first_mouse = self.accept_first_mouse;
4507 let tabbing_identifier = opt_str_lit(self.tabbing_identifier.as_ref());
4508 let additional_browser_args = opt_str_lit(self.additional_browser_args.as_ref());
4509 let shadow = self.shadow;
4510 let window_effects = opt_lit(self.window_effects.as_ref());
4511 let incognito = self.incognito;
4512 let parent = opt_str_lit(self.parent.as_ref());
4513 let zoom_hotkeys_enabled = self.zoom_hotkeys_enabled;
4514 let browser_extensions_enabled = self.browser_extensions_enabled;
4515 let use_https_scheme = self.use_https_scheme;
4516 let devtools = opt_lit(self.devtools.as_ref());
4517 let background_color = opt_lit(self.background_color.as_ref());
4518 let background_throttling = opt_lit(self.background_throttling.as_ref());
4519 let javascript_disabled = self.javascript_disabled;
4520 let allow_link_preview = self.allow_link_preview;
4521 let disable_input_accessory_view = self.disable_input_accessory_view;
4522 let data_directory = opt_lit(self.data_directory.as_ref().map(path_buf_lit).as_ref());
4523 let data_store_identifier = opt_vec_lit(self.data_store_identifier, identity);
4524 let scroll_bar_style = &self.scroll_bar_style;
4525 let limit_navigations_to_app_bound_domains = self.limit_navigations_to_app_bound_domains;
4526 let activity_name = opt_lit(self.activity_name.as_ref());
4527 let created_by_activity_name = opt_lit(self.created_by_activity_name.as_ref());
4528 let requested_by_scene_identifier = opt_lit(self.requested_by_scene_identifier.as_ref());
4529 let general_autofill_enabled = self.general_autofill_enabled;
4530
4531 literal_struct!(
4532 tokens,
4533 ::tauri::utils::config::WindowConfig,
4534 label,
4535 url,
4536 create,
4537 user_agent,
4538 drag_drop_enabled,
4539 center,
4540 x,
4541 y,
4542 width,
4543 height,
4544 min_width,
4545 min_height,
4546 max_width,
4547 max_height,
4548 prevent_overflow,
4549 resizable,
4550 maximizable,
4551 minimizable,
4552 closable,
4553 title,
4554 proxy_url,
4555 fullscreen,
4556 focus,
4557 focusable,
4558 transparent,
4559 maximized,
4560 visible,
4561 decorations,
4562 always_on_bottom,
4563 always_on_top,
4564 visible_on_all_workspaces,
4565 content_protected,
4566 skip_taskbar,
4567 window_classname,
4568 no_redirection_bitmap,
4569 theme,
4570 title_bar_style,
4571 traffic_light_position,
4572 hidden_title,
4573 accept_first_mouse,
4574 tabbing_identifier,
4575 additional_browser_args,
4576 shadow,
4577 window_effects,
4578 incognito,
4579 parent,
4580 zoom_hotkeys_enabled,
4581 browser_extensions_enabled,
4582 use_https_scheme,
4583 devtools,
4584 background_color,
4585 background_throttling,
4586 javascript_disabled,
4587 allow_link_preview,
4588 disable_input_accessory_view,
4589 data_directory,
4590 data_store_identifier,
4591 scroll_bar_style,
4592 limit_navigations_to_app_bound_domains,
4593 activity_name,
4594 created_by_activity_name,
4595 requested_by_scene_identifier,
4596 general_autofill_enabled
4597 );
4598 }
4599 }
4600
4601 impl ToTokens for PatternKind {
4602 fn to_tokens(&self, tokens: &mut TokenStream) {
4603 let prefix = quote! { ::tauri::utils::config::PatternKind };
4604
4605 tokens.append_all(match self {
4606 Self::Brownfield => quote! { #prefix::Brownfield },
4607 #[cfg(not(feature = "isolation"))]
4608 Self::Isolation { dir: _ } => quote! { #prefix::Brownfield },
4609 #[cfg(feature = "isolation")]
4610 Self::Isolation { dir } => {
4611 let dir = path_buf_lit(dir);
4612 quote! { #prefix::Isolation { dir: #dir } }
4613 }
4614 })
4615 }
4616 }
4617
4618 impl ToTokens for WebviewInstallMode {
4619 fn to_tokens(&self, tokens: &mut TokenStream) {
4620 let prefix = quote! { ::tauri::utils::config::WebviewInstallMode };
4621
4622 tokens.append_all(match self {
4623 Self::Skip => quote! { #prefix::Skip },
4624 Self::DownloadBootstrapper { silent } => {
4625 quote! { #prefix::DownloadBootstrapper { silent: #silent } }
4626 }
4627 Self::EmbedBootstrapper { silent } => {
4628 quote! { #prefix::EmbedBootstrapper { silent: #silent } }
4629 }
4630 Self::OfflineInstaller { silent } => {
4631 quote! { #prefix::OfflineInstaller { silent: #silent } }
4632 }
4633 Self::FixedRuntime { path } => {
4634 let path = path_buf_lit(path);
4635 quote! { #prefix::FixedRuntime { path: #path } }
4636 }
4637 })
4638 }
4639 }
4640
4641 impl ToTokens for WindowsConfig {
4642 fn to_tokens(&self, tokens: &mut TokenStream) {
4643 let webview_install_mode = &self.webview_install_mode;
4644 tokens.append_all(quote! { ::tauri::utils::config::WindowsConfig {
4645 webview_install_mode: #webview_install_mode,
4646 ..Default::default()
4647 }})
4648 }
4649 }
4650
4651 impl ToTokens for BundleResources {
4652 fn to_tokens(&self, tokens: &mut TokenStream) {
4653 let prefix = quote! { ::tauri::utils::config::BundleResources };
4654
4655 tokens.append_all(match self {
4656 Self::List(paths) => {
4657 let paths = vec_lit(paths, str_lit);
4658 quote! { #prefix::List(#paths) }
4659 }
4660 Self::Map(map) => {
4661 let map = map_lit(
4662 quote! { ::std::collections::HashMap },
4663 map,
4664 str_lit,
4665 str_lit,
4666 );
4667 quote! { #prefix::Map(#map) }
4668 }
4669 })
4670 }
4671 }
4672
4673 impl ToTokens for BundleConfig {
4674 fn to_tokens(&self, tokens: &mut TokenStream) {
4675 let publisher = quote!(None);
4676 let homepage = quote!(None);
4677 let icon = vec_lit(&self.icon, str_lit);
4678 let active = self.active;
4679 let targets = quote!(Default::default());
4680 let create_updater_artifacts = quote!(Default::default());
4681 let resources = opt_lit(self.resources.as_ref());
4682 let copyright = quote!(None);
4683 let category = quote!(None);
4684 let file_associations = quote!(None);
4685 let short_description = quote!(None);
4686 let long_description = quote!(None);
4687 let use_local_tools_dir = self.use_local_tools_dir;
4688 let external_bin = opt_vec_lit(self.external_bin.as_ref(), str_lit);
4689 let windows = &self.windows;
4690 let license = opt_str_lit(self.license.as_ref());
4691 let license_file = opt_lit(self.license_file.as_ref().map(path_buf_lit).as_ref());
4692 let linux = quote!(Default::default());
4693 let macos = quote!(Default::default());
4694 let ios = quote!(Default::default());
4695 let android = quote!(Default::default());
4696 let cef = quote!(Default::default());
4697
4698 literal_struct!(
4699 tokens,
4700 ::tauri::utils::config::BundleConfig,
4701 active,
4702 publisher,
4703 homepage,
4704 icon,
4705 targets,
4706 create_updater_artifacts,
4707 resources,
4708 copyright,
4709 category,
4710 license,
4711 license_file,
4712 file_associations,
4713 short_description,
4714 long_description,
4715 use_local_tools_dir,
4716 external_bin,
4717 windows,
4718 linux,
4719 macos,
4720 ios,
4721 android,
4722 cef
4723 );
4724 }
4725 }
4726
4727 impl ToTokens for FrontendDist {
4728 fn to_tokens(&self, tokens: &mut TokenStream) {
4729 let prefix = quote! { ::tauri::utils::config::FrontendDist };
4730
4731 tokens.append_all(match self {
4732 Self::Url(url) => {
4733 let url = url_lit(url);
4734 quote! { #prefix::Url(#url) }
4735 }
4736 Self::Directory(path) => {
4737 let path = path_buf_lit(path);
4738 quote! { #prefix::Directory(#path) }
4739 }
4740 Self::Files(files) => {
4741 let files = vec_lit(files, path_buf_lit);
4742 quote! { #prefix::Files(#files) }
4743 }
4744 })
4745 }
4746 }
4747
4748 impl ToTokens for RunnerConfig {
4749 fn to_tokens(&self, tokens: &mut TokenStream) {
4750 let prefix = quote! { ::tauri::utils::config::RunnerConfig };
4751
4752 tokens.append_all(match self {
4753 Self::String(cmd) => {
4754 let cmd = cmd.as_str();
4755 quote!(#prefix::String(#cmd.into()))
4756 }
4757 Self::Object { cmd, cwd, args } => {
4758 let cmd = cmd.as_str();
4759 let cwd = opt_str_lit(cwd.as_ref());
4760 let args = opt_lit(args.as_ref().map(|v| vec_lit(v, str_lit)).as_ref());
4761 quote!(#prefix::Object {
4762 cmd: #cmd.into(),
4763 cwd: #cwd,
4764 args: #args,
4765 })
4766 }
4767 })
4768 }
4769 }
4770
4771 impl ToTokens for BuildConfig {
4772 fn to_tokens(&self, tokens: &mut TokenStream) {
4773 let dev_url = opt_lit(self.dev_url.as_ref().map(url_lit).as_ref());
4774 let frontend_dist = opt_lit(self.frontend_dist.as_ref());
4775 let runner = opt_lit(self.runner.as_ref());
4776 let before_dev_command = quote!(None);
4777 let before_build_command = quote!(None);
4778 let before_bundle_command = quote!(None);
4779 let features = quote!(None);
4780 let remove_unused_commands = quote!(false);
4781 let additional_watch_folders = quote!(Vec::new());
4782 let windows = &self.windows;
4783
4784 literal_struct!(
4785 tokens,
4786 ::tauri::utils::config::BuildConfig,
4787 runner,
4788 dev_url,
4789 frontend_dist,
4790 before_dev_command,
4791 before_build_command,
4792 before_bundle_command,
4793 features,
4794 remove_unused_commands,
4795 additional_watch_folders,
4796 windows
4797 );
4798 }
4799 }
4800
4801 impl ToTokens for WindowsBuildConfig {
4802 fn to_tokens(&self, tokens: &mut TokenStream) {
4803 let static_vc_runtime = self.static_vc_runtime;
4804
4805 literal_struct!(
4806 tokens,
4807 ::tauri::utils::config::WindowsBuildConfig,
4808 static_vc_runtime
4809 );
4810 }
4811 }
4812
4813 impl ToTokens for CspDirectiveSources {
4814 fn to_tokens(&self, tokens: &mut TokenStream) {
4815 let prefix = quote! { ::tauri::utils::config::CspDirectiveSources };
4816
4817 tokens.append_all(match self {
4818 Self::Inline(sources) => {
4819 let sources = sources.as_str();
4820 quote!(#prefix::Inline(#sources.into()))
4821 }
4822 Self::List(list) => {
4823 let list = vec_lit(list, str_lit);
4824 quote!(#prefix::List(#list))
4825 }
4826 })
4827 }
4828 }
4829
4830 impl ToTokens for Csp {
4831 fn to_tokens(&self, tokens: &mut TokenStream) {
4832 let prefix = quote! { ::tauri::utils::config::Csp };
4833
4834 tokens.append_all(match self {
4835 Self::Policy(policy) => {
4836 let policy = policy.as_str();
4837 quote!(#prefix::Policy(#policy.into()))
4838 }
4839 Self::DirectiveMap(list) => {
4840 let mut sorted: Vec<_> = list.iter().collect();
4844 sorted.sort_by_key(|(k, _)| *k);
4845 let map = map_lit(
4846 quote! { ::std::collections::HashMap },
4847 sorted,
4848 str_lit,
4849 identity,
4850 );
4851 quote!(#prefix::DirectiveMap(#map))
4852 }
4853 })
4854 }
4855 }
4856
4857 impl ToTokens for DisabledCspModificationKind {
4858 fn to_tokens(&self, tokens: &mut TokenStream) {
4859 let prefix = quote! { ::tauri::utils::config::DisabledCspModificationKind };
4860
4861 tokens.append_all(match self {
4862 Self::Flag(flag) => {
4863 quote! { #prefix::Flag(#flag) }
4864 }
4865 Self::List(directives) => {
4866 let directives = vec_lit(directives, str_lit);
4867 quote! { #prefix::List(#directives) }
4868 }
4869 });
4870 }
4871 }
4872
4873 impl ToTokens for CapabilityEntry {
4874 fn to_tokens(&self, tokens: &mut TokenStream) {
4875 let prefix = quote! { ::tauri::utils::config::CapabilityEntry };
4876
4877 tokens.append_all(match self {
4878 Self::Inlined(capability) => {
4879 quote! { #prefix::Inlined(#capability) }
4880 }
4881 Self::Reference(id) => {
4882 let id = str_lit(id);
4883 quote! { #prefix::Reference(#id) }
4884 }
4885 });
4886 }
4887 }
4888
4889 impl ToTokens for HeaderSource {
4890 fn to_tokens(&self, tokens: &mut TokenStream) {
4891 let prefix = quote! { ::tauri::utils::config::HeaderSource };
4892
4893 tokens.append_all(match self {
4894 Self::Inline(s) => {
4895 let line = s.as_str();
4896 quote!(#prefix::Inline(#line.into()))
4897 }
4898 Self::List(l) => {
4899 let list = vec_lit(l, str_lit);
4900 quote!(#prefix::List(#list))
4901 }
4902 Self::Map(m) => {
4903 let mut sorted: Vec<_> = m.iter().collect();
4907 sorted.sort_by_key(|(k, _)| *k);
4908 let map = map_lit(
4909 quote! { ::std::collections::HashMap },
4910 sorted,
4911 str_lit,
4912 str_lit,
4913 );
4914 quote!(#prefix::Map(#map))
4915 }
4916 })
4917 }
4918 }
4919
4920 impl ToTokens for HeaderConfig {
4921 fn to_tokens(&self, tokens: &mut TokenStream) {
4922 let access_control_allow_credentials =
4923 opt_lit(self.access_control_allow_credentials.as_ref());
4924 let access_control_allow_headers = opt_lit(self.access_control_allow_headers.as_ref());
4925 let access_control_allow_methods = opt_lit(self.access_control_allow_methods.as_ref());
4926 let access_control_expose_headers = opt_lit(self.access_control_expose_headers.as_ref());
4927 let access_control_max_age = opt_lit(self.access_control_max_age.as_ref());
4928 let cross_origin_embedder_policy = opt_lit(self.cross_origin_embedder_policy.as_ref());
4929 let cross_origin_opener_policy = opt_lit(self.cross_origin_opener_policy.as_ref());
4930 let cross_origin_resource_policy = opt_lit(self.cross_origin_resource_policy.as_ref());
4931 let permissions_policy = opt_lit(self.permissions_policy.as_ref());
4932 let service_worker_allowed = opt_lit(self.service_worker_allowed.as_ref());
4933 let timing_allow_origin = opt_lit(self.timing_allow_origin.as_ref());
4934 let x_content_type_options = opt_lit(self.x_content_type_options.as_ref());
4935 let tauri_custom_header = opt_lit(self.tauri_custom_header.as_ref());
4936
4937 literal_struct!(
4938 tokens,
4939 ::tauri::utils::config::HeaderConfig,
4940 access_control_allow_credentials,
4941 access_control_allow_headers,
4942 access_control_allow_methods,
4943 access_control_expose_headers,
4944 access_control_max_age,
4945 cross_origin_embedder_policy,
4946 cross_origin_opener_policy,
4947 cross_origin_resource_policy,
4948 permissions_policy,
4949 service_worker_allowed,
4950 timing_allow_origin,
4951 x_content_type_options,
4952 tauri_custom_header
4953 );
4954 }
4955 }
4956
4957 impl ToTokens for SecurityConfig {
4958 fn to_tokens(&self, tokens: &mut TokenStream) {
4959 let csp = opt_lit(self.csp.as_ref());
4960 let dev_csp = opt_lit(self.dev_csp.as_ref());
4961 let freeze_prototype = self.freeze_prototype;
4962 let dangerous_disable_asset_csp_modification = &self.dangerous_disable_asset_csp_modification;
4963 let asset_protocol = &self.asset_protocol;
4964 let pattern = &self.pattern;
4965 let capabilities = vec_lit(&self.capabilities, identity);
4966 let headers = opt_lit(self.headers.as_ref());
4967
4968 literal_struct!(
4969 tokens,
4970 ::tauri::utils::config::SecurityConfig,
4971 csp,
4972 dev_csp,
4973 freeze_prototype,
4974 dangerous_disable_asset_csp_modification,
4975 asset_protocol,
4976 pattern,
4977 capabilities,
4978 headers
4979 );
4980 }
4981 }
4982
4983 impl ToTokens for TrayIconConfig {
4984 fn to_tokens(&self, tokens: &mut TokenStream) {
4985 tokens.append_all(quote!(#[allow(deprecated)]));
4987
4988 let id = opt_str_lit(self.id.as_ref());
4989 let icon_as_template = self.icon_as_template;
4990 #[allow(deprecated)]
4991 let menu_on_left_click = self.menu_on_left_click;
4992 let show_menu_on_left_click = self.show_menu_on_left_click;
4993 let icon_path = path_buf_lit(&self.icon_path);
4994 let title = opt_str_lit(self.title.as_ref());
4995 let tooltip = opt_str_lit(self.tooltip.as_ref());
4996 literal_struct!(
4997 tokens,
4998 ::tauri::utils::config::TrayIconConfig,
4999 id,
5000 icon_path,
5001 icon_as_template,
5002 menu_on_left_click,
5003 show_menu_on_left_click,
5004 title,
5005 tooltip
5006 );
5007 }
5008 }
5009
5010 impl ToTokens for FsScope {
5011 fn to_tokens(&self, tokens: &mut TokenStream) {
5012 let prefix = quote! { ::tauri::utils::config::FsScope };
5013
5014 tokens.append_all(match self {
5015 Self::AllowedPaths(allow) => {
5016 let allowed_paths = vec_lit(allow, path_buf_lit);
5017 quote! { #prefix::AllowedPaths(#allowed_paths) }
5018 }
5019 Self::Scope { allow, deny , require_literal_leading_dot} => {
5020 let allow = vec_lit(allow, path_buf_lit);
5021 let deny = vec_lit(deny, path_buf_lit);
5022 let require_literal_leading_dot = opt_lit(require_literal_leading_dot.as_ref());
5023 quote! { #prefix::Scope { allow: #allow, deny: #deny, require_literal_leading_dot: #require_literal_leading_dot } }
5024 }
5025 });
5026 }
5027 }
5028
5029 impl ToTokens for AssetProtocolConfig {
5030 fn to_tokens(&self, tokens: &mut TokenStream) {
5031 let scope = &self.scope;
5032 tokens.append_all(quote! { ::tauri::utils::config::AssetProtocolConfig { scope: #scope, ..Default::default() } })
5033 }
5034 }
5035
5036 impl ToTokens for AppDirectoryOverrides {
5037 fn to_tokens(&self, tokens: &mut TokenStream) {
5038 let config = opt_lit_owned(self.config.as_ref().map(path_buf_lit));
5039 let data = opt_lit_owned(self.data.as_ref().map(path_buf_lit));
5040 let local_data = opt_lit_owned(self.local_data.as_ref().map(path_buf_lit));
5041 let cache = opt_lit_owned(self.cache.as_ref().map(path_buf_lit));
5042 let log = opt_lit_owned(self.log.as_ref().map(path_buf_lit));
5043
5044 literal_struct!(
5045 tokens,
5046 ::tauri::utils::config::AppDirectoryOverrides,
5047 config,
5048 data,
5049 local_data,
5050 cache,
5051 log
5052 );
5053 }
5054 }
5055
5056 impl ToTokens for AppDirectoriesOverride {
5057 fn to_tokens(&self, tokens: &mut TokenStream) {
5058 let prefix = quote! { ::tauri::utils::config::AppDirectoriesOverride };
5059
5060 tokens.append_all(match self {
5061 Self::Root(root) => {
5062 let root = path_buf_lit(root);
5063 quote! { #prefix::Root(#root) }
5064 }
5065 Self::Directories(directories) => quote! { #prefix::Directories(#directories) },
5066 })
5067 }
5068 }
5069
5070 impl ToTokens for AppConfig {
5071 fn to_tokens(&self, tokens: &mut TokenStream) {
5072 let windows = vec_lit(&self.windows, identity);
5073 let security = &self.security;
5074 let tray_icon = opt_lit(self.tray_icon.as_ref());
5075 let with_global_tauri = self.with_global_tauri;
5076 let enable_gtk_app_id = self.enable_gtk_app_id;
5077 let app_directories_override = opt_lit(self.app_directories_override.as_ref());
5078
5079 literal_struct!(
5080 tokens,
5081 ::tauri::utils::config::AppConfig,
5082 windows,
5083 security,
5084 tray_icon,
5085 with_global_tauri,
5086 enable_gtk_app_id,
5087 app_directories_override
5088 );
5089 }
5090 }
5091
5092 impl ToTokens for PluginConfig {
5093 fn to_tokens(&self, tokens: &mut TokenStream) {
5094 let mut sorted: Vec<_> = self.0.iter().collect();
5098 sorted.sort_by_key(|(k, _)| *k);
5099 let config = map_lit(
5100 quote! { ::std::collections::HashMap },
5101 sorted,
5102 str_lit,
5103 json_value_lit,
5104 );
5105 tokens.append_all(quote! { ::tauri::utils::config::PluginConfig(#config) })
5106 }
5107 }
5108
5109 impl ToTokens for Config {
5110 fn to_tokens(&self, tokens: &mut TokenStream) {
5111 let schema = quote!(None);
5112 let product_name = opt_str_lit(self.product_name.as_ref());
5113 let main_binary_name = opt_str_lit(self.main_binary_name.as_ref());
5114 let version = opt_str_lit(self.version.as_ref());
5115 let identifier = str_lit(&self.identifier);
5116 let app = &self.app;
5117 let build = &self.build;
5118 let bundle = &self.bundle;
5119 let plugins = &self.plugins;
5120
5121 literal_struct!(
5122 tokens,
5123 ::tauri::utils::config::Config,
5124 schema,
5125 product_name,
5126 main_binary_name,
5127 version,
5128 identifier,
5129 app,
5130 build,
5131 bundle,
5132 plugins
5133 );
5134 }
5135 }
5136}
5137
5138#[cfg(test)]
5139mod test {
5140 use super::*;
5141
5142 #[test]
5145 fn test_defaults() {
5147 let a_config = AppConfig::default();
5149 let b_config = BuildConfig::default();
5151 let d_windows: Vec<WindowConfig> = vec![];
5153 let d_bundle = BundleConfig::default();
5155
5156 let app = AppConfig {
5158 windows: vec![],
5159 security: SecurityConfig {
5160 csp: None,
5161 dev_csp: None,
5162 freeze_prototype: false,
5163 dangerous_disable_asset_csp_modification: DisabledCspModificationKind::Flag(false),
5164 asset_protocol: AssetProtocolConfig::default(),
5165 pattern: Default::default(),
5166 capabilities: Vec::new(),
5167 headers: None,
5168 },
5169 tray_icon: None,
5170 with_global_tauri: false,
5171 enable_gtk_app_id: false,
5172 app_directories_override: None,
5173 };
5174
5175 let build = BuildConfig {
5177 runner: None,
5178 dev_url: None,
5179 frontend_dist: None,
5180 before_dev_command: None,
5181 before_build_command: None,
5182 before_bundle_command: None,
5183 features: None,
5184 remove_unused_commands: false,
5185 additional_watch_folders: Vec::new(),
5186 windows: WindowsBuildConfig::default(),
5187 };
5188
5189 let bundle = BundleConfig {
5191 active: false,
5192 targets: Default::default(),
5193 create_updater_artifacts: Default::default(),
5194 publisher: None,
5195 homepage: None,
5196 icon: Vec::new(),
5197 resources: None,
5198 copyright: None,
5199 category: None,
5200 file_associations: None,
5201 short_description: None,
5202 long_description: None,
5203 use_local_tools_dir: false,
5204 license: None,
5205 license_file: None,
5206 linux: Default::default(),
5207 macos: Default::default(),
5208 external_bin: None,
5209 windows: Default::default(),
5210 ios: Default::default(),
5211 android: Default::default(),
5212 cef: Default::default(),
5213 };
5214
5215 assert_eq!(a_config, app);
5217 assert_eq!(b_config, build);
5218 assert_eq!(d_bundle, bundle);
5219 assert_eq!(d_windows, app.windows);
5220 }
5221
5222 #[test]
5223 fn app_directories_override_root() {
5224 let config: AppDirectoriesOverride = serde_json::from_str(r#""./""#).unwrap();
5225 assert_eq!(config, AppDirectoriesOverride::Root("./".into()));
5226
5227 let config: AppDirectoriesOverride = serde_json::from_str(r#""$DATA/my-app""#).unwrap();
5228 assert_eq!(config, AppDirectoriesOverride::Root("$DATA/my-app".into()));
5229 }
5230
5231 #[test]
5232 fn app_directories_override_directories() {
5233 let config: AppDirectoriesOverride = serde_json::from_str(
5234 r#"{ "log": "$DATA/logs", "cache": "$CACHE/my-app", "local-data": "data" }"#,
5235 )
5236 .unwrap();
5237 assert_eq!(
5238 config,
5239 AppDirectoriesOverride::Directories(AppDirectoryOverrides {
5240 config: None,
5241 data: None,
5242 local_data: Some("data".into()),
5243 cache: Some("$CACHE/my-app".into()),
5244 log: Some("$DATA/logs".into()),
5245 })
5246 );
5247
5248 let config: AppDirectoriesOverride =
5249 serde_json::from_str(r#"{ "config": "conf", "data": "data", "localData": "local" }"#)
5250 .unwrap();
5251 assert_eq!(
5252 config,
5253 AppDirectoriesOverride::Directories(AppDirectoryOverrides {
5254 config: Some("conf".into()),
5255 data: Some("data".into()),
5256 local_data: Some("local".into()),
5257 cache: None,
5258 log: None,
5259 })
5260 );
5261
5262 let config: AppDirectoriesOverride = serde_json::from_str("{}").unwrap();
5263 assert_eq!(
5264 config,
5265 AppDirectoriesOverride::Directories(AppDirectoryOverrides::default())
5266 );
5267 }
5268
5269 #[test]
5270 fn app_directories_override_rejects_unknown_directories() {
5271 let err = serde_json::from_str::<AppDirectoriesOverride>(r#"{ "logs": "x" }"#).unwrap_err();
5272 assert!(err.to_string().contains("unknown field `logs`"), "{err}");
5273 }
5274
5275 #[test]
5276 fn app_directories_override_accepts_supported_variables() {
5277 for variable in APP_DIRECTORIES_OVERRIDE_VARIABLES {
5278 for path in [
5279 variable.to_string(),
5280 format!("{variable}/my-app"),
5281 format!("{variable}/../my-app"),
5282 ] {
5283 let json = serde_json::to_string(&path).unwrap();
5284 let config: AppDirectoriesOverride = serde_json::from_str(&json).unwrap();
5285 assert_eq!(config, AppDirectoriesOverride::Root(path.into()));
5286 }
5287 }
5288 }
5289
5290 #[test]
5291 fn app_directories_override_rejects_unsupported_variables() {
5292 for variable in [
5293 "$APPCONFIG",
5294 "$APPDATA",
5295 "$APPLOCALDATA",
5296 "$APPCACHE",
5297 "$APPLOG",
5298 "$EXE",
5299 "$FONT",
5300 "$RESOURCE",
5301 "$RUNTIME",
5302 "$TEMPLATE",
5303 "$UNKNOWN",
5304 ] {
5305 let err = serde_json::from_str::<AppDirectoriesOverride>(&format!(r#""{variable}/my-app""#))
5306 .unwrap_err();
5307 assert!(
5308 err
5309 .to_string()
5310 .contains(&format!("unsupported base directory variable `{variable}`")),
5311 "{variable}: {err}"
5312 );
5313
5314 let err =
5315 serde_json::from_str::<AppDirectoriesOverride>(&format!(r#"{{ "log": "{variable}" }}"#))
5316 .unwrap_err();
5317 assert!(
5318 err
5319 .to_string()
5320 .contains("unsupported base directory variable"),
5321 "{variable}: {err}"
5322 );
5323 }
5324 }
5325
5326 #[cfg(windows)]
5327 #[test]
5328 fn app_directories_override_rejects_root_relative_paths() {
5329 for path in [r"\my-app", "C:my-app"] {
5330 let json = serde_json::to_string(path).unwrap();
5331 let err = serde_json::from_str::<AppDirectoriesOverride>(&json).unwrap_err();
5332 assert!(
5333 err.to_string().contains("must be an absolute path"),
5334 "{path}: {err}"
5335 );
5336 }
5337 }
5338
5339 #[cfg(feature = "build")]
5340 #[test]
5341 fn app_directories_override_to_tokens() {
5342 use quote::ToTokens;
5343
5344 let tokens = AppDirectoriesOverride::Root("./".into())
5345 .to_token_stream()
5346 .to_string()
5347 .replace(' ', "");
5348 assert_eq!(
5349 tokens,
5350 r#"::tauri::utils::config::AppDirectoriesOverride::Root(::std::path::PathBuf::from("./"))"#
5351 );
5352
5353 let tokens = AppDirectoriesOverride::Directories(AppDirectoryOverrides {
5354 log: Some("$DATA/logs".into()),
5355 ..Default::default()
5356 })
5357 .to_token_stream()
5358 .to_string()
5359 .replace(' ', "");
5360 assert_eq!(
5361 tokens,
5362 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"))})"#
5363 );
5364 }
5365
5366 #[test]
5367 fn parse_hex_color() {
5368 use super::Color;
5369
5370 assert_eq!(Color(255, 255, 255, 255), "fff".parse().unwrap());
5371 assert_eq!(Color(255, 255, 255, 255), "#fff".parse().unwrap());
5372 assert_eq!(Color(0, 0, 0, 255), "#000000".parse().unwrap());
5373 assert_eq!(Color(0, 0, 0, 255), "#000000ff".parse().unwrap());
5374 assert_eq!(Color(0, 255, 0, 255), "#00ff00ff".parse().unwrap());
5375 }
5376
5377 #[test]
5378 fn test_runner_config_string_format() {
5379 use super::RunnerConfig;
5380
5381 let json = r#""cargo""#;
5383 let runner: RunnerConfig = serde_json::from_str(json).unwrap();
5384
5385 assert_eq!(runner.cmd(), "cargo");
5386 assert_eq!(runner.cwd(), None);
5387 assert_eq!(runner.args(), None);
5388
5389 let serialized = serde_json::to_string(&runner).unwrap();
5391 assert_eq!(serialized, r#""cargo""#);
5392 }
5393
5394 #[test]
5395 fn test_runner_config_object_format_full() {
5396 use super::RunnerConfig;
5397
5398 let json = r#"{"cmd": "my_runner", "cwd": "/tmp/build", "args": ["--quiet", "--verbose"]}"#;
5400 let runner: RunnerConfig = serde_json::from_str(json).unwrap();
5401
5402 assert_eq!(runner.cmd(), "my_runner");
5403 assert_eq!(runner.cwd(), Some("/tmp/build"));
5404 assert_eq!(
5405 runner.args(),
5406 Some(&["--quiet".to_string(), "--verbose".to_string()][..])
5407 );
5408
5409 let serialized = serde_json::to_string(&runner).unwrap();
5411 let deserialized: RunnerConfig = serde_json::from_str(&serialized).unwrap();
5412 assert_eq!(runner, deserialized);
5413 }
5414
5415 #[test]
5416 fn test_runner_config_object_format_minimal() {
5417 use super::RunnerConfig;
5418
5419 let json = r#"{"cmd": "cross"}"#;
5421 let runner: RunnerConfig = serde_json::from_str(json).unwrap();
5422
5423 assert_eq!(runner.cmd(), "cross");
5424 assert_eq!(runner.cwd(), None);
5425 assert_eq!(runner.args(), None);
5426 }
5427
5428 #[test]
5429 fn test_runner_config_default() {
5430 use super::RunnerConfig;
5431
5432 let default_runner = RunnerConfig::default();
5433 assert_eq!(default_runner.cmd(), "cargo");
5434 assert_eq!(default_runner.cwd(), None);
5435 assert_eq!(default_runner.args(), None);
5436 }
5437
5438 #[test]
5439 fn test_runner_config_from_str() {
5440 use super::RunnerConfig;
5441
5442 let runner: RunnerConfig = "my_runner".into();
5444 assert_eq!(runner.cmd(), "my_runner");
5445 assert_eq!(runner.cwd(), None);
5446 assert_eq!(runner.args(), None);
5447 }
5448
5449 #[test]
5450 fn test_runner_config_from_string() {
5451 use super::RunnerConfig;
5452
5453 let runner: RunnerConfig = "another_runner".to_string().into();
5455 assert_eq!(runner.cmd(), "another_runner");
5456 assert_eq!(runner.cwd(), None);
5457 assert_eq!(runner.args(), None);
5458 }
5459
5460 #[test]
5461 fn test_runner_config_from_str_parse() {
5462 use super::RunnerConfig;
5463 use std::str::FromStr;
5464
5465 let runner = RunnerConfig::from_str("parsed_runner").unwrap();
5467 assert_eq!(runner.cmd(), "parsed_runner");
5468 assert_eq!(runner.cwd(), None);
5469 assert_eq!(runner.args(), None);
5470 }
5471
5472 #[test]
5473 fn test_runner_config_in_build_config() {
5474 use super::BuildConfig;
5475
5476 let json = r#"{"runner": "cargo"}"#;
5478 let build_config: BuildConfig = serde_json::from_str(json).unwrap();
5479
5480 let runner = build_config.runner.unwrap();
5481 assert_eq!(runner.cmd(), "cargo");
5482 assert_eq!(runner.cwd(), None);
5483 assert_eq!(runner.args(), None);
5484 }
5485
5486 #[test]
5487 fn test_runner_config_in_build_config_object() {
5488 use super::BuildConfig;
5489
5490 let json = r#"{"runner": {"cmd": "cross", "cwd": "/workspace", "args": ["--target", "x86_64-unknown-linux-gnu"]}}"#;
5492 let build_config: BuildConfig = serde_json::from_str(json).unwrap();
5493
5494 let runner = build_config.runner.unwrap();
5495 assert_eq!(runner.cmd(), "cross");
5496 assert_eq!(runner.cwd(), Some("/workspace"));
5497 assert_eq!(
5498 runner.args(),
5499 Some(
5500 &[
5501 "--target".to_string(),
5502 "x86_64-unknown-linux-gnu".to_string()
5503 ][..]
5504 )
5505 );
5506 }
5507
5508 #[test]
5509 fn test_runner_config_in_full_config() {
5510 use super::Config;
5511
5512 let json = r#"{
5514 "productName": "Test App",
5515 "version": "1.0.0",
5516 "identifier": "com.test.app",
5517 "build": {
5518 "runner": {
5519 "cmd": "my_custom_cargo",
5520 "cwd": "/tmp/build",
5521 "args": ["--quiet", "--verbose"]
5522 }
5523 }
5524 }"#;
5525
5526 let config: Config = serde_json::from_str(json).unwrap();
5527 let runner = config.build.runner.unwrap();
5528
5529 assert_eq!(runner.cmd(), "my_custom_cargo");
5530 assert_eq!(runner.cwd(), Some("/tmp/build"));
5531 assert_eq!(
5532 runner.args(),
5533 Some(&["--quiet".to_string(), "--verbose".to_string()][..])
5534 );
5535 }
5536
5537 #[test]
5538 fn test_runner_config_equality() {
5539 use super::RunnerConfig;
5540
5541 let runner1 = RunnerConfig::String("cargo".to_string());
5542 let runner2 = RunnerConfig::String("cargo".to_string());
5543 let runner3 = RunnerConfig::String("cross".to_string());
5544
5545 assert_eq!(runner1, runner2);
5546 assert_ne!(runner1, runner3);
5547
5548 let runner4 = RunnerConfig::Object {
5549 cmd: "cargo".to_string(),
5550 cwd: Some("/tmp".to_string()),
5551 args: Some(vec!["--quiet".to_string()]),
5552 };
5553 let runner5 = RunnerConfig::Object {
5554 cmd: "cargo".to_string(),
5555 cwd: Some("/tmp".to_string()),
5556 args: Some(vec!["--quiet".to_string()]),
5557 };
5558
5559 assert_eq!(runner4, runner5);
5560 assert_ne!(runner1, runner4);
5561 }
5562
5563 #[test]
5564 fn test_runner_config_untagged_serialization() {
5565 use super::RunnerConfig;
5566
5567 let string_runner = RunnerConfig::String("cargo".to_string());
5569 let string_json = serde_json::to_string(&string_runner).unwrap();
5570 assert_eq!(string_json, r#""cargo""#);
5571
5572 let object_runner = RunnerConfig::Object {
5574 cmd: "cross".to_string(),
5575 cwd: None,
5576 args: None,
5577 };
5578 let object_json = serde_json::to_string(&object_runner).unwrap();
5579 assert!(object_json.contains("\"cmd\":\"cross\""));
5580 assert!(object_json.contains("\"cwd\":null") || !object_json.contains("cwd"));
5582 assert!(object_json.contains("\"args\":null") || !object_json.contains("args"));
5583 }
5584
5585 #[test]
5586 fn header_source_map_display_is_deterministic() {
5587 let map = HashMap::from([
5588 ("key3".to_string(), "'value3'".to_string()),
5589 ("key1".to_string(), "'value1' 'value2'".to_string()),
5590 ("key2".to_string(), "'value4'".to_string()),
5591 ]);
5592
5593 assert_eq!(
5595 HeaderSource::Map(map.clone()).to_string(),
5596 "key1 'value1' 'value2'; key2 'value4'; key3 'value3'"
5597 );
5598
5599 let expected = HeaderSource::Map(map).to_string();
5600 for _ in 0..10 {
5601 let map = HashMap::from([
5602 ("key2".to_string(), "'value4'".to_string()),
5603 ("key3".to_string(), "'value3'".to_string()),
5604 ("key1".to_string(), "'value1' 'value2'".to_string()),
5605 ]);
5606 assert_eq!(HeaderSource::Map(map).to_string(), expected);
5607 }
5608
5609 let map = HashMap::from([
5611 ("b".to_string(), "2".to_string()),
5612 ("a".to_string(), "1".to_string()),
5613 ]);
5614 assert_eq!(
5615 serde_json::to_string(&HeaderSource::Map(map)).unwrap(),
5616 r#"{"a":"1","b":"2"}"#
5617 );
5618 }
5619
5620 #[test]
5621 fn header_source_display() {
5622 assert_eq!(
5623 HeaderSource::Inline("same-origin".into()).to_string(),
5624 "same-origin"
5625 );
5626 assert_eq!(
5627 HeaderSource::List(vec!["https://a.example".into(), "https://b.example".into()]).to_string(),
5628 "https://a.example, https://b.example"
5629 );
5630 }
5631
5632 #[test]
5633 fn window_config_default_same_as_deserialize() {
5634 let config_from_deserialization: WindowConfig = serde_json::from_str("{}").unwrap();
5635 let config_from_default: WindowConfig = WindowConfig::default();
5636
5637 assert_eq!(config_from_deserialization, config_from_default);
5638 }
5639}