1use http::response::Builder;
27#[cfg(feature = "schema")]
28use schemars::JsonSchema;
29use semver::Version;
30use serde::{
31 de::{Deserializer, Error as DeError, Visitor},
32 Deserialize, Serialize, Serializer,
33};
34use serde_json::Value as JsonValue;
35use serde_untagged::UntaggedEnumVisitor;
36use serde_with::skip_serializing_none;
37use url::Url;
38
39use std::{
40 collections::HashMap,
41 fmt::{self, Display},
42 fs::read_to_string,
43 path::PathBuf,
44 str::FromStr,
45};
46
47pub mod parse;
49
50use crate::{acl::capability::Capability, TitleBarStyle, WindowEffect, WindowEffectState};
51
52pub use self::parse::parse;
53
54fn default_true() -> bool {
55 true
56}
57
58#[derive(PartialEq, Eq, Debug, Clone, Serialize)]
60#[cfg_attr(feature = "schema", derive(JsonSchema))]
61#[serde(untagged)]
62#[non_exhaustive]
63pub enum WebviewUrl {
64 External(Url),
66 App(PathBuf),
70 CustomProtocol(Url),
72}
73
74impl<'de> Deserialize<'de> for WebviewUrl {
75 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
76 where
77 D: Deserializer<'de>,
78 {
79 #[derive(Deserialize)]
80 #[serde(untagged)]
81 enum WebviewUrlDeserializer {
82 Url(Url),
83 Path(PathBuf),
84 }
85
86 match WebviewUrlDeserializer::deserialize(deserializer)? {
87 WebviewUrlDeserializer::Url(u) => {
88 if u.scheme() == "https" || u.scheme() == "http" {
89 Ok(Self::External(u))
90 } else {
91 Ok(Self::CustomProtocol(u))
92 }
93 }
94 WebviewUrlDeserializer::Path(p) => Ok(Self::App(p)),
95 }
96 }
97}
98
99impl fmt::Display for WebviewUrl {
100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101 match self {
102 Self::External(url) | Self::CustomProtocol(url) => write!(f, "{url}"),
103 Self::App(path) => write!(f, "{}", path.display()),
104 }
105 }
106}
107
108impl Default for WebviewUrl {
109 fn default() -> Self {
110 Self::App("index.html".into())
111 }
112}
113
114#[derive(Debug, PartialEq, Eq, Clone)]
116#[cfg_attr(feature = "schema", derive(JsonSchema))]
117#[cfg_attr(feature = "schema", schemars(rename_all = "lowercase"))]
118pub enum BundleType {
119 Deb,
121 Rpm,
123 AppImage,
125 Msi,
127 Nsis,
129 App,
131 Dmg,
133}
134
135impl BundleType {
136 fn all() -> &'static [Self] {
138 &[
139 BundleType::Deb,
140 BundleType::Rpm,
141 BundleType::AppImage,
142 BundleType::Msi,
143 BundleType::Nsis,
144 BundleType::App,
145 BundleType::Dmg,
146 ]
147 }
148}
149
150impl Display for BundleType {
151 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152 write!(
153 f,
154 "{}",
155 match self {
156 Self::Deb => "deb",
157 Self::Rpm => "rpm",
158 Self::AppImage => "appimage",
159 Self::Msi => "msi",
160 Self::Nsis => "nsis",
161 Self::App => "app",
162 Self::Dmg => "dmg",
163 }
164 )
165 }
166}
167
168impl Serialize for BundleType {
169 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
170 where
171 S: Serializer,
172 {
173 serializer.serialize_str(self.to_string().as_ref())
174 }
175}
176
177impl<'de> Deserialize<'de> for BundleType {
178 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
179 where
180 D: Deserializer<'de>,
181 {
182 let s = String::deserialize(deserializer)?;
183 match s.to_lowercase().as_str() {
184 "deb" => Ok(Self::Deb),
185 "rpm" => Ok(Self::Rpm),
186 "appimage" => Ok(Self::AppImage),
187 "msi" => Ok(Self::Msi),
188 "nsis" => Ok(Self::Nsis),
189 "app" => Ok(Self::App),
190 "dmg" => Ok(Self::Dmg),
191 _ => Err(DeError::custom(format!("unknown bundle target '{s}'"))),
192 }
193 }
194}
195
196#[derive(Debug, PartialEq, Eq, Clone)]
198pub enum BundleTarget {
199 All,
201 List(Vec<BundleType>),
203 One(BundleType),
205}
206
207#[cfg(feature = "schema")]
208impl schemars::JsonSchema for BundleTarget {
209 fn schema_name() -> std::string::String {
210 "BundleTarget".to_owned()
211 }
212
213 fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
214 let any_of = vec![
215 schemars::schema::SchemaObject {
216 const_value: Some("all".into()),
217 metadata: Some(Box::new(schemars::schema::Metadata {
218 description: Some("Bundle all targets.".to_owned()),
219 ..Default::default()
220 })),
221 ..Default::default()
222 }
223 .into(),
224 schemars::_private::metadata::add_description(
225 gen.subschema_for::<Vec<BundleType>>(),
226 "A list of bundle targets.",
227 ),
228 schemars::_private::metadata::add_description(
229 gen.subschema_for::<BundleType>(),
230 "A single bundle target.",
231 ),
232 ];
233
234 schemars::schema::SchemaObject {
235 subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
236 any_of: Some(any_of),
237 ..Default::default()
238 })),
239 metadata: Some(Box::new(schemars::schema::Metadata {
240 description: Some("Targets to bundle. Each value is case insensitive.".to_owned()),
241 ..Default::default()
242 })),
243 ..Default::default()
244 }
245 .into()
246 }
247}
248
249impl Default for BundleTarget {
250 fn default() -> Self {
251 Self::All
252 }
253}
254
255impl Serialize for BundleTarget {
256 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
257 where
258 S: Serializer,
259 {
260 match self {
261 Self::All => serializer.serialize_str("all"),
262 Self::List(l) => l.serialize(serializer),
263 Self::One(t) => serializer.serialize_str(t.to_string().as_ref()),
264 }
265 }
266}
267
268impl<'de> Deserialize<'de> for BundleTarget {
269 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
270 where
271 D: Deserializer<'de>,
272 {
273 #[derive(Deserialize, Serialize)]
274 #[serde(untagged)]
275 pub enum BundleTargetInner {
276 List(Vec<BundleType>),
277 One(BundleType),
278 All(String),
279 }
280
281 match BundleTargetInner::deserialize(deserializer)? {
282 BundleTargetInner::All(s) if s.to_lowercase() == "all" => Ok(Self::All),
283 BundleTargetInner::All(t) => Err(DeError::custom(format!(
284 "invalid bundle type {t}, expected one of `all`, {}",
285 BundleType::all()
286 .iter()
287 .map(|b| format!("`{b}`"))
288 .collect::<Vec<_>>()
289 .join(", ")
290 ))),
291 BundleTargetInner::List(l) => Ok(Self::List(l)),
292 BundleTargetInner::One(t) => Ok(Self::One(t)),
293 }
294 }
295}
296
297impl BundleTarget {
298 #[allow(dead_code)]
300 pub fn to_vec(&self) -> Vec<BundleType> {
301 match self {
302 Self::All => BundleType::all().to_vec(),
303 Self::List(list) => list.clone(),
304 Self::One(i) => vec![i.clone()],
305 }
306 }
307}
308
309#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
313#[cfg_attr(feature = "schema", derive(JsonSchema))]
314#[serde(rename_all = "camelCase", deny_unknown_fields)]
315pub struct AppImageConfig {
316 #[serde(default, alias = "bundle-media-framework")]
319 pub bundle_media_framework: bool,
320 #[serde(default)]
322 pub files: HashMap<PathBuf, PathBuf>,
323}
324
325#[skip_serializing_none]
329#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
330#[cfg_attr(feature = "schema", derive(JsonSchema))]
331#[serde(rename_all = "camelCase", deny_unknown_fields)]
332pub struct DebConfig {
333 pub depends: Option<Vec<String>>,
335 pub recommends: Option<Vec<String>>,
337 pub provides: Option<Vec<String>>,
339 pub conflicts: Option<Vec<String>>,
341 pub replaces: Option<Vec<String>>,
343 #[serde(default)]
345 pub files: HashMap<PathBuf, PathBuf>,
346 pub section: Option<String>,
348 pub priority: Option<String>,
351 pub changelog: Option<PathBuf>,
354 #[serde(alias = "desktop-template")]
358 pub desktop_template: Option<PathBuf>,
359 #[serde(alias = "pre-install-script")]
362 pub pre_install_script: Option<PathBuf>,
363 #[serde(alias = "post-install-script")]
366 pub post_install_script: Option<PathBuf>,
367 #[serde(alias = "pre-remove-script")]
370 pub pre_remove_script: Option<PathBuf>,
371 #[serde(alias = "post-remove-script")]
374 pub post_remove_script: Option<PathBuf>,
375}
376
377#[skip_serializing_none]
381#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
382#[cfg_attr(feature = "schema", derive(JsonSchema))]
383#[serde(rename_all = "camelCase", deny_unknown_fields)]
384pub struct LinuxConfig {
385 #[serde(default)]
387 pub appimage: AppImageConfig,
388 #[serde(default)]
390 pub deb: DebConfig,
391 #[serde(default)]
393 pub rpm: RpmConfig,
394}
395
396#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
398#[cfg_attr(feature = "schema", derive(JsonSchema))]
399#[serde(rename_all = "camelCase", deny_unknown_fields, tag = "type")]
400#[non_exhaustive]
401pub enum RpmCompression {
402 Gzip {
404 level: u32,
406 },
407 Zstd {
409 level: i32,
411 },
412 Xz {
414 level: u32,
416 },
417 Bzip2 {
419 level: u32,
421 },
422 None,
424}
425
426#[skip_serializing_none]
428#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
429#[cfg_attr(feature = "schema", derive(JsonSchema))]
430#[serde(rename_all = "camelCase", deny_unknown_fields)]
431pub struct RpmConfig {
432 pub depends: Option<Vec<String>>,
434 pub recommends: Option<Vec<String>>,
436 pub provides: Option<Vec<String>>,
438 pub conflicts: Option<Vec<String>>,
441 pub obsoletes: Option<Vec<String>>,
444 #[serde(default = "default_release")]
446 pub release: String,
447 #[serde(default)]
449 pub epoch: u32,
450 #[serde(default)]
452 pub files: HashMap<PathBuf, PathBuf>,
453 #[serde(alias = "desktop-template")]
457 pub desktop_template: Option<PathBuf>,
458 #[serde(alias = "pre-install-script")]
461 pub pre_install_script: Option<PathBuf>,
462 #[serde(alias = "post-install-script")]
465 pub post_install_script: Option<PathBuf>,
466 #[serde(alias = "pre-remove-script")]
469 pub pre_remove_script: Option<PathBuf>,
470 #[serde(alias = "post-remove-script")]
473 pub post_remove_script: Option<PathBuf>,
474 pub compression: Option<RpmCompression>,
476}
477
478impl Default for RpmConfig {
479 fn default() -> Self {
480 Self {
481 depends: None,
482 recommends: None,
483 provides: None,
484 conflicts: None,
485 obsoletes: None,
486 release: default_release(),
487 epoch: 0,
488 files: Default::default(),
489 desktop_template: None,
490 pre_install_script: None,
491 post_install_script: None,
492 pre_remove_script: None,
493 post_remove_script: None,
494 compression: None,
495 }
496 }
497}
498
499fn default_release() -> String {
500 "1".into()
501}
502
503#[derive(Default, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
505#[cfg_attr(feature = "schema", derive(JsonSchema))]
506#[serde(rename_all = "camelCase", deny_unknown_fields)]
507pub struct Position {
508 pub x: u32,
510 pub y: u32,
512}
513
514#[derive(Default, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
516#[cfg_attr(feature = "schema", derive(JsonSchema))]
517#[serde(rename_all = "camelCase", deny_unknown_fields)]
518pub struct Size {
519 pub width: u32,
521 pub height: u32,
523}
524
525#[skip_serializing_none]
529#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
530#[cfg_attr(feature = "schema", derive(JsonSchema))]
531#[serde(rename_all = "camelCase", deny_unknown_fields)]
532pub struct DmgConfig {
533 pub background: Option<PathBuf>,
535 pub window_position: Option<Position>,
537 #[serde(default = "dmg_window_size", alias = "window-size")]
539 pub window_size: Size,
540 #[serde(default = "dmg_app_position", alias = "app-position")]
542 pub app_position: Position,
543 #[serde(
545 default = "dmg_application_folder_position",
546 alias = "application-folder-position"
547 )]
548 pub application_folder_position: Position,
549}
550
551impl Default for DmgConfig {
552 fn default() -> Self {
553 Self {
554 background: None,
555 window_position: None,
556 window_size: dmg_window_size(),
557 app_position: dmg_app_position(),
558 application_folder_position: dmg_application_folder_position(),
559 }
560 }
561}
562
563fn dmg_window_size() -> Size {
564 Size {
565 width: 660,
566 height: 400,
567 }
568}
569
570fn dmg_app_position() -> Position {
571 Position { x: 180, y: 170 }
572}
573
574fn dmg_application_folder_position() -> Position {
575 Position { x: 480, y: 170 }
576}
577
578fn de_macos_minimum_system_version<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
579where
580 D: Deserializer<'de>,
581{
582 let version = Option::<String>::deserialize(deserializer)?;
583 match version {
584 Some(v) if v.is_empty() => Ok(macos_minimum_system_version()),
585 e => Ok(e),
586 }
587}
588
589#[skip_serializing_none]
593#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
594#[cfg_attr(feature = "schema", derive(JsonSchema))]
595#[serde(rename_all = "camelCase", deny_unknown_fields)]
596pub struct MacConfig {
597 pub frameworks: Option<Vec<String>>,
601 #[serde(default)]
603 pub files: HashMap<PathBuf, PathBuf>,
604 #[serde(
611 deserialize_with = "de_macos_minimum_system_version",
612 default = "macos_minimum_system_version",
613 alias = "minimum-system-version"
614 )]
615 pub minimum_system_version: Option<String>,
616 #[serde(alias = "exception-domain")]
619 pub exception_domain: Option<String>,
620 #[serde(alias = "signing-identity")]
622 pub signing_identity: Option<String>,
623 #[serde(alias = "hardened-runtime", default = "default_true")]
627 pub hardened_runtime: bool,
628 #[serde(alias = "provider-short-name")]
630 pub provider_short_name: Option<String>,
631 pub entitlements: Option<String>,
633 #[serde(default)]
635 pub dmg: DmgConfig,
636}
637
638impl Default for MacConfig {
639 fn default() -> Self {
640 Self {
641 frameworks: None,
642 files: HashMap::new(),
643 minimum_system_version: macos_minimum_system_version(),
644 exception_domain: None,
645 signing_identity: None,
646 hardened_runtime: true,
647 provider_short_name: None,
648 entitlements: None,
649 dmg: Default::default(),
650 }
651 }
652}
653
654fn macos_minimum_system_version() -> Option<String> {
655 Some("10.13".into())
656}
657
658fn ios_minimum_system_version() -> String {
659 "13.0".into()
660}
661
662#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
666#[cfg_attr(feature = "schema", derive(JsonSchema))]
667#[serde(rename_all = "camelCase", deny_unknown_fields)]
668pub struct WixLanguageConfig {
669 #[serde(alias = "locale-path")]
671 pub locale_path: Option<String>,
672}
673
674#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
676#[cfg_attr(feature = "schema", derive(JsonSchema))]
677#[serde(untagged)]
678pub enum WixLanguage {
679 One(String),
681 List(Vec<String>),
683 Localized(HashMap<String, WixLanguageConfig>),
685}
686
687impl Default for WixLanguage {
688 fn default() -> Self {
689 Self::One("en-US".into())
690 }
691}
692
693#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
697#[cfg_attr(feature = "schema", derive(JsonSchema))]
698#[serde(rename_all = "camelCase", deny_unknown_fields)]
699pub struct WixConfig {
700 pub version: Option<String>,
709 #[serde(alias = "upgrade-code")]
718 pub upgrade_code: Option<uuid::Uuid>,
719 #[serde(default)]
721 pub language: WixLanguage,
722 pub template: Option<PathBuf>,
724 #[serde(default, alias = "fragment-paths")]
726 pub fragment_paths: Vec<PathBuf>,
727 #[serde(default, alias = "component-group-refs")]
729 pub component_group_refs: Vec<String>,
730 #[serde(default, alias = "component-refs")]
732 pub component_refs: Vec<String>,
733 #[serde(default, alias = "feature-group-refs")]
735 pub feature_group_refs: Vec<String>,
736 #[serde(default, alias = "feature-refs")]
738 pub feature_refs: Vec<String>,
739 #[serde(default, alias = "merge-refs")]
741 pub merge_refs: Vec<String>,
742 #[serde(default, alias = "enable-elevated-update-task")]
744 pub enable_elevated_update_task: bool,
745 #[serde(alias = "banner-path")]
750 pub banner_path: Option<PathBuf>,
751 #[serde(alias = "dialog-image-path")]
756 pub dialog_image_path: Option<PathBuf>,
757}
758
759#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
763#[cfg_attr(feature = "schema", derive(JsonSchema))]
764#[serde(rename_all = "camelCase", deny_unknown_fields)]
765pub enum NsisCompression {
766 Zlib,
768 Bzip2,
770 Lzma,
772 None,
774}
775
776impl Default for NsisCompression {
777 fn default() -> Self {
778 Self::Lzma
779 }
780}
781
782#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
784#[serde(rename_all = "camelCase", deny_unknown_fields)]
785#[cfg_attr(feature = "schema", derive(JsonSchema))]
786pub enum NSISInstallerMode {
787 CurrentUser,
793 PerMachine,
798 Both,
804}
805
806impl Default for NSISInstallerMode {
807 fn default() -> Self {
808 Self::CurrentUser
809 }
810}
811
812#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
814#[cfg_attr(feature = "schema", derive(JsonSchema))]
815#[serde(rename_all = "camelCase", deny_unknown_fields)]
816pub struct NsisConfig {
817 pub template: Option<PathBuf>,
819 #[serde(alias = "header-image")]
823 pub header_image: Option<PathBuf>,
824 #[serde(alias = "sidebar-image")]
828 pub sidebar_image: Option<PathBuf>,
829 #[serde(alias = "install-icon")]
831 pub installer_icon: Option<PathBuf>,
832 #[serde(default, alias = "install-mode")]
834 pub install_mode: NSISInstallerMode,
835 pub languages: Option<Vec<String>>,
841 pub custom_language_files: Option<HashMap<String, PathBuf>>,
848 #[serde(default, alias = "display-language-selector")]
851 pub display_language_selector: bool,
852 #[serde(default)]
856 pub compression: NsisCompression,
857 #[serde(alias = "start-menu-folder")]
866 pub start_menu_folder: Option<String>,
867 #[serde(alias = "installer-hooks")]
898 pub installer_hooks: Option<PathBuf>,
899 #[serde(alias = "minimum-webview2-version")]
903 pub minimum_webview2_version: Option<String>,
904}
905
906#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
911#[serde(tag = "type", rename_all = "camelCase", deny_unknown_fields)]
912#[cfg_attr(feature = "schema", derive(JsonSchema))]
913pub enum WebviewInstallMode {
914 Skip,
916 DownloadBootstrapper {
920 #[serde(default = "default_true")]
922 silent: bool,
923 },
924 EmbedBootstrapper {
928 #[serde(default = "default_true")]
930 silent: bool,
931 },
932 OfflineInstaller {
936 #[serde(default = "default_true")]
938 silent: bool,
939 },
940 FixedRuntime {
943 path: PathBuf,
948 },
949}
950
951impl Default for WebviewInstallMode {
952 fn default() -> Self {
953 Self::DownloadBootstrapper { silent: true }
954 }
955}
956
957#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
959#[cfg_attr(feature = "schema", derive(JsonSchema))]
960#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
961pub enum CustomSignCommandConfig {
962 Command(String),
971 CommandWithOptions {
976 cmd: String,
978 args: Vec<String>,
982 },
983}
984
985#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
989#[cfg_attr(feature = "schema", derive(JsonSchema))]
990#[serde(rename_all = "camelCase", deny_unknown_fields)]
991pub struct WindowsConfig {
992 #[serde(alias = "digest-algorithm")]
995 pub digest_algorithm: Option<String>,
996 #[serde(alias = "certificate-thumbprint")]
998 pub certificate_thumbprint: Option<String>,
999 #[serde(alias = "timestamp-url")]
1001 pub timestamp_url: Option<String>,
1002 #[serde(default)]
1005 pub tsp: bool,
1006 #[serde(default, alias = "webview-install-mode")]
1008 pub webview_install_mode: WebviewInstallMode,
1009 #[serde(default = "default_true", alias = "allow-downgrades")]
1015 pub allow_downgrades: bool,
1016 pub wix: Option<WixConfig>,
1018 pub nsis: Option<NsisConfig>,
1020 #[serde(alias = "sign-command")]
1028 pub sign_command: Option<CustomSignCommandConfig>,
1029}
1030
1031impl Default for WindowsConfig {
1032 fn default() -> Self {
1033 Self {
1034 digest_algorithm: None,
1035 certificate_thumbprint: None,
1036 timestamp_url: None,
1037 tsp: false,
1038 webview_install_mode: Default::default(),
1039 allow_downgrades: true,
1040 wix: None,
1041 nsis: None,
1042 sign_command: None,
1043 }
1044 }
1045}
1046
1047#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1049#[cfg_attr(feature = "schema", derive(JsonSchema))]
1050pub enum BundleTypeRole {
1051 #[default]
1053 Editor,
1054 Viewer,
1056 Shell,
1058 QLGenerator,
1060 None,
1062}
1063
1064impl Display for BundleTypeRole {
1065 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1066 match self {
1067 Self::Editor => write!(f, "Editor"),
1068 Self::Viewer => write!(f, "Viewer"),
1069 Self::Shell => write!(f, "Shell"),
1070 Self::QLGenerator => write!(f, "QLGenerator"),
1071 Self::None => write!(f, "None"),
1072 }
1073 }
1074}
1075
1076#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
1080#[cfg_attr(feature = "schema", derive(JsonSchema))]
1081pub struct AssociationExt(pub String);
1082
1083impl fmt::Display for AssociationExt {
1084 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1085 write!(f, "{}", self.0)
1086 }
1087}
1088
1089impl<'d> serde::Deserialize<'d> for AssociationExt {
1090 fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {
1091 let ext = String::deserialize(deserializer)?;
1092 if let Some(ext) = ext.strip_prefix('.') {
1093 Ok(AssociationExt(ext.into()))
1094 } else {
1095 Ok(AssociationExt(ext))
1096 }
1097 }
1098}
1099
1100#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1102#[cfg_attr(feature = "schema", derive(JsonSchema))]
1103#[serde(rename_all = "camelCase", deny_unknown_fields)]
1104pub struct FileAssociation {
1105 pub ext: Vec<AssociationExt>,
1107 pub name: Option<String>,
1109 pub description: Option<String>,
1111 #[serde(default)]
1113 pub role: BundleTypeRole,
1114 #[serde(alias = "mime-type")]
1116 pub mime_type: Option<String>,
1117}
1118
1119#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1121#[cfg_attr(feature = "schema", derive(JsonSchema))]
1122#[serde(rename_all = "camelCase", deny_unknown_fields)]
1123pub struct DeepLinkProtocol {
1124 pub schemes: Vec<String>,
1126 pub name: Option<String>,
1128 #[serde(default)]
1130 pub role: BundleTypeRole,
1131}
1132
1133#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1136#[cfg_attr(feature = "schema", derive(JsonSchema))]
1137#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
1138pub enum BundleResources {
1139 List(Vec<String>),
1141 Map(HashMap<String, String>),
1143}
1144
1145impl BundleResources {
1146 pub fn push(&mut self, path: impl Into<String>) {
1148 match self {
1149 Self::List(l) => l.push(path.into()),
1150 Self::Map(l) => {
1151 let path = path.into();
1152 l.insert(path.clone(), path);
1153 }
1154 }
1155 }
1156}
1157
1158#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1160#[cfg_attr(feature = "schema", derive(JsonSchema))]
1161#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
1162pub enum Updater {
1163 String(V1Compatible),
1165 Bool(bool),
1168}
1169
1170impl Default for Updater {
1171 fn default() -> Self {
1172 Self::Bool(false)
1173 }
1174}
1175
1176#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1178#[cfg_attr(feature = "schema", derive(JsonSchema))]
1179#[serde(rename_all = "camelCase", deny_unknown_fields)]
1180pub enum V1Compatible {
1181 V1Compatible,
1183}
1184
1185#[skip_serializing_none]
1189#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1190#[cfg_attr(feature = "schema", derive(JsonSchema))]
1191#[serde(rename_all = "camelCase", deny_unknown_fields)]
1192pub struct BundleConfig {
1193 #[serde(default)]
1195 pub active: bool,
1196 #[serde(default)]
1198 pub targets: BundleTarget,
1199 #[serde(default)]
1200 pub create_updater_artifacts: Updater,
1202 pub publisher: Option<String>,
1207 pub homepage: Option<String>,
1212 #[serde(default)]
1214 pub icon: Vec<String>,
1215 pub resources: Option<BundleResources>,
1219 pub copyright: Option<String>,
1221 pub license: Option<String>,
1224 #[serde(alias = "license-file")]
1226 pub license_file: Option<PathBuf>,
1227 pub category: Option<String>,
1232 pub file_associations: Option<Vec<FileAssociation>>,
1234 #[serde(alias = "short-description")]
1236 pub short_description: Option<String>,
1237 #[serde(alias = "long-description")]
1239 pub long_description: Option<String>,
1240 #[serde(default, alias = "use-local-tools-dir")]
1248 pub use_local_tools_dir: bool,
1249 #[serde(alias = "external-bin")]
1261 pub external_bin: Option<Vec<String>>,
1262 #[serde(default)]
1264 pub windows: WindowsConfig,
1265 #[serde(default)]
1267 pub linux: LinuxConfig,
1268 #[serde(rename = "macOS", alias = "macos", default)]
1270 pub macos: MacConfig,
1271 #[serde(rename = "iOS", alias = "ios", default)]
1273 pub ios: IosConfig,
1274 #[serde(default)]
1276 pub android: AndroidConfig,
1277}
1278
1279#[derive(Debug, PartialEq, Eq, Serialize, Default, Clone, Copy)]
1281#[serde(rename_all = "camelCase", deny_unknown_fields)]
1282pub struct Color(pub u8, pub u8, pub u8, pub u8);
1283
1284impl From<Color> for (u8, u8, u8, u8) {
1285 fn from(value: Color) -> Self {
1286 (value.0, value.1, value.2, value.3)
1287 }
1288}
1289
1290impl From<Color> for (u8, u8, u8) {
1291 fn from(value: Color) -> Self {
1292 (value.0, value.1, value.2)
1293 }
1294}
1295
1296impl From<(u8, u8, u8, u8)> for Color {
1297 fn from(value: (u8, u8, u8, u8)) -> Self {
1298 Color(value.0, value.1, value.2, value.3)
1299 }
1300}
1301
1302impl From<(u8, u8, u8)> for Color {
1303 fn from(value: (u8, u8, u8)) -> Self {
1304 Color(value.0, value.1, value.2, 255)
1305 }
1306}
1307
1308impl From<Color> for [u8; 4] {
1309 fn from(value: Color) -> Self {
1310 [value.0, value.1, value.2, value.3]
1311 }
1312}
1313
1314impl From<Color> for [u8; 3] {
1315 fn from(value: Color) -> Self {
1316 [value.0, value.1, value.2]
1317 }
1318}
1319
1320impl From<[u8; 4]> for Color {
1321 fn from(value: [u8; 4]) -> Self {
1322 Color(value[0], value[1], value[2], value[3])
1323 }
1324}
1325
1326impl From<[u8; 3]> for Color {
1327 fn from(value: [u8; 3]) -> Self {
1328 Color(value[0], value[1], value[2], 255)
1329 }
1330}
1331
1332impl FromStr for Color {
1333 type Err = String;
1334 fn from_str(mut color: &str) -> Result<Self, Self::Err> {
1335 color = color.trim().strip_prefix('#').unwrap_or(color);
1336 let color = match color.len() {
1337 3 => color.chars()
1339 .flat_map(|c| std::iter::repeat(c).take(2))
1340 .chain(std::iter::repeat('f').take(2))
1341 .collect(),
1342 6 => format!("{color}FF"),
1343 8 => color.to_string(),
1344 _ => return Err("Invalid hex color length, must be either 3, 6 or 8, for example: #fff, #ffffff, or #ffffffff".into()),
1345 };
1346
1347 let r = u8::from_str_radix(&color[0..2], 16).map_err(|e| e.to_string())?;
1348 let g = u8::from_str_radix(&color[2..4], 16).map_err(|e| e.to_string())?;
1349 let b = u8::from_str_radix(&color[4..6], 16).map_err(|e| e.to_string())?;
1350 let a = u8::from_str_radix(&color[6..8], 16).map_err(|e| e.to_string())?;
1351
1352 Ok(Color(r, g, b, a))
1353 }
1354}
1355
1356fn default_alpha() -> u8 {
1357 255
1358}
1359
1360#[derive(Deserialize)]
1361#[cfg_attr(feature = "schema", derive(JsonSchema))]
1362#[serde(untagged)]
1363enum InnerColor {
1364 String(String),
1366 Rgb((u8, u8, u8)),
1368 Rgba((u8, u8, u8, u8)),
1370 RgbaObject {
1372 red: u8,
1373 green: u8,
1374 blue: u8,
1375 #[serde(default = "default_alpha")]
1376 alpha: u8,
1377 },
1378}
1379
1380impl<'de> Deserialize<'de> for Color {
1381 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1382 where
1383 D: Deserializer<'de>,
1384 {
1385 let color = InnerColor::deserialize(deserializer)?;
1386 let color = match color {
1387 InnerColor::String(string) => string.parse().map_err(serde::de::Error::custom)?,
1388 InnerColor::Rgb(rgb) => Color(rgb.0, rgb.1, rgb.2, 255),
1389 InnerColor::Rgba(rgb) => rgb.into(),
1390 InnerColor::RgbaObject {
1391 red,
1392 green,
1393 blue,
1394 alpha,
1395 } => Color(red, green, blue, alpha),
1396 };
1397
1398 Ok(color)
1399 }
1400}
1401
1402#[cfg(feature = "schema")]
1403impl schemars::JsonSchema for Color {
1404 fn schema_name() -> String {
1405 "Color".to_string()
1406 }
1407
1408 fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
1409 let mut schema = schemars::schema_for!(InnerColor).schema;
1410 schema.metadata = None; let any_of = schema.subschemas().any_of.as_mut().unwrap();
1414 let schemars::schema::Schema::Object(str_schema) = any_of.first_mut().unwrap() else {
1415 unreachable!()
1416 };
1417 str_schema.string().pattern = Some("^#?([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$".into());
1418
1419 schema.into()
1420 }
1421}
1422
1423#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1425#[cfg_attr(feature = "schema", derive(JsonSchema))]
1426#[serde(rename_all = "camelCase", deny_unknown_fields)]
1427pub enum BackgroundThrottlingPolicy {
1428 Disabled,
1430 Suspend,
1432 Throttle,
1434}
1435
1436#[skip_serializing_none]
1438#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default)]
1439#[cfg_attr(feature = "schema", derive(JsonSchema))]
1440#[serde(rename_all = "camelCase", deny_unknown_fields)]
1441pub struct WindowEffectsConfig {
1442 pub effects: Vec<WindowEffect>,
1445 pub state: Option<WindowEffectState>,
1447 pub radius: Option<f64>,
1449 pub color: Option<Color>,
1452}
1453
1454#[skip_serializing_none]
1458#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
1459#[cfg_attr(feature = "schema", derive(JsonSchema))]
1460#[serde(rename_all = "camelCase", deny_unknown_fields)]
1461pub struct WindowConfig {
1462 #[serde(default = "default_window_label")]
1464 pub label: String,
1465 #[serde(default = "default_true")]
1470 pub create: bool,
1471 #[serde(default)]
1473 pub url: WebviewUrl,
1474 #[serde(alias = "user-agent")]
1476 pub user_agent: Option<String>,
1477 #[serde(default = "default_true", alias = "drag-drop-enabled")]
1481 pub drag_drop_enabled: bool,
1482 #[serde(default)]
1484 pub center: bool,
1485 pub x: Option<f64>,
1487 pub y: Option<f64>,
1489 #[serde(default = "default_width")]
1491 pub width: f64,
1492 #[serde(default = "default_height")]
1494 pub height: f64,
1495 #[serde(alias = "min-width")]
1497 pub min_width: Option<f64>,
1498 #[serde(alias = "min-height")]
1500 pub min_height: Option<f64>,
1501 #[serde(alias = "max-width")]
1503 pub max_width: Option<f64>,
1504 #[serde(alias = "max-height")]
1506 pub max_height: Option<f64>,
1507 #[serde(default = "default_true")]
1509 pub resizable: bool,
1510 #[serde(default = "default_true")]
1518 pub maximizable: bool,
1519 #[serde(default = "default_true")]
1525 pub minimizable: bool,
1526 #[serde(default = "default_true")]
1534 pub closable: bool,
1535 #[serde(default = "default_title")]
1537 pub title: String,
1538 #[serde(default)]
1540 pub fullscreen: bool,
1541 #[serde(default = "default_true")]
1543 pub focus: bool,
1544 #[serde(default)]
1549 pub transparent: bool,
1550 #[serde(default)]
1552 pub maximized: bool,
1553 #[serde(default = "default_true")]
1555 pub visible: bool,
1556 #[serde(default = "default_true")]
1558 pub decorations: bool,
1559 #[serde(default, alias = "always-on-bottom")]
1561 pub always_on_bottom: bool,
1562 #[serde(default, alias = "always-on-top")]
1564 pub always_on_top: bool,
1565 #[serde(default, alias = "visible-on-all-workspaces")]
1571 pub visible_on_all_workspaces: bool,
1572 #[serde(default, alias = "content-protected")]
1574 pub content_protected: bool,
1575 #[serde(default, alias = "skip-taskbar")]
1577 pub skip_taskbar: bool,
1578 pub window_classname: Option<String>,
1580 pub theme: Option<crate::Theme>,
1582 #[serde(default, alias = "title-bar-style")]
1584 pub title_bar_style: TitleBarStyle,
1585 #[serde(default, alias = "hidden-title")]
1587 pub hidden_title: bool,
1588 #[serde(default, alias = "accept-first-mouse")]
1590 pub accept_first_mouse: bool,
1591 #[serde(default, alias = "tabbing-identifier")]
1598 pub tabbing_identifier: Option<String>,
1599 #[serde(default, alias = "additional-browser-args")]
1602 pub additional_browser_args: Option<String>,
1603 #[serde(default = "default_true")]
1613 pub shadow: bool,
1614 #[serde(default, alias = "window-effects")]
1623 pub window_effects: Option<WindowEffectsConfig>,
1624 #[serde(default)]
1630 pub incognito: bool,
1631 pub parent: Option<String>,
1643 #[serde(alias = "proxy-url")]
1651 pub proxy_url: Option<Url>,
1652 #[serde(default, alias = "zoom-hotkeys-enabled")]
1662 pub zoom_hotkeys_enabled: bool,
1663 #[serde(default, alias = "browser-extensions-enabled")]
1670 pub browser_extensions_enabled: bool,
1671
1672 #[serde(default, alias = "use-https-scheme")]
1682 pub use_https_scheme: bool,
1683 pub devtools: Option<bool>,
1693
1694 #[serde(alias = "background-color")]
1702 pub background_color: Option<Color>,
1703
1704 #[serde(default, alias = "background-throttling")]
1719 pub background_throttling: Option<BackgroundThrottlingPolicy>,
1720}
1721
1722impl Default for WindowConfig {
1723 fn default() -> Self {
1724 Self {
1725 label: default_window_label(),
1726 url: WebviewUrl::default(),
1727 create: true,
1728 user_agent: None,
1729 drag_drop_enabled: true,
1730 center: false,
1731 x: None,
1732 y: None,
1733 width: default_width(),
1734 height: default_height(),
1735 min_width: None,
1736 min_height: None,
1737 max_width: None,
1738 max_height: None,
1739 resizable: true,
1740 maximizable: true,
1741 minimizable: true,
1742 closable: true,
1743 title: default_title(),
1744 fullscreen: false,
1745 focus: false,
1746 transparent: false,
1747 maximized: false,
1748 visible: true,
1749 decorations: true,
1750 always_on_bottom: false,
1751 always_on_top: false,
1752 visible_on_all_workspaces: false,
1753 content_protected: false,
1754 skip_taskbar: false,
1755 window_classname: None,
1756 theme: None,
1757 title_bar_style: Default::default(),
1758 hidden_title: false,
1759 accept_first_mouse: false,
1760 tabbing_identifier: None,
1761 additional_browser_args: None,
1762 shadow: true,
1763 window_effects: None,
1764 incognito: false,
1765 parent: None,
1766 proxy_url: None,
1767 zoom_hotkeys_enabled: false,
1768 browser_extensions_enabled: false,
1769 use_https_scheme: false,
1770 devtools: None,
1771 background_color: None,
1772 background_throttling: None,
1773 }
1774 }
1775}
1776
1777fn default_window_label() -> String {
1778 "main".to_string()
1779}
1780
1781fn default_width() -> f64 {
1782 800f64
1783}
1784
1785fn default_height() -> f64 {
1786 600f64
1787}
1788
1789fn default_title() -> String {
1790 "Tauri App".to_string()
1791}
1792
1793#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1796#[cfg_attr(feature = "schema", derive(JsonSchema))]
1797#[serde(rename_all = "camelCase", untagged)]
1798pub enum CspDirectiveSources {
1799 Inline(String),
1801 List(Vec<String>),
1803}
1804
1805impl Default for CspDirectiveSources {
1806 fn default() -> Self {
1807 Self::List(Vec::new())
1808 }
1809}
1810
1811impl From<CspDirectiveSources> for Vec<String> {
1812 fn from(sources: CspDirectiveSources) -> Self {
1813 match sources {
1814 CspDirectiveSources::Inline(source) => source.split(' ').map(|s| s.to_string()).collect(),
1815 CspDirectiveSources::List(l) => l,
1816 }
1817 }
1818}
1819
1820impl CspDirectiveSources {
1821 pub fn contains(&self, source: &str) -> bool {
1823 match self {
1824 Self::Inline(s) => s.contains(&format!("{source} ")) || s.contains(&format!(" {source}")),
1825 Self::List(l) => l.contains(&source.into()),
1826 }
1827 }
1828
1829 pub fn push<S: AsRef<str>>(&mut self, source: S) {
1831 match self {
1832 Self::Inline(s) => {
1833 s.push(' ');
1834 s.push_str(source.as_ref());
1835 }
1836 Self::List(l) => {
1837 l.push(source.as_ref().to_string());
1838 }
1839 }
1840 }
1841
1842 pub fn extend(&mut self, sources: Vec<String>) {
1844 for s in sources {
1845 self.push(s);
1846 }
1847 }
1848}
1849
1850#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1853#[cfg_attr(feature = "schema", derive(JsonSchema))]
1854#[serde(rename_all = "camelCase", untagged)]
1855pub enum Csp {
1856 Policy(String),
1858 DirectiveMap(HashMap<String, CspDirectiveSources>),
1860}
1861
1862impl From<HashMap<String, CspDirectiveSources>> for Csp {
1863 fn from(map: HashMap<String, CspDirectiveSources>) -> Self {
1864 Self::DirectiveMap(map)
1865 }
1866}
1867
1868impl From<Csp> for HashMap<String, CspDirectiveSources> {
1869 fn from(csp: Csp) -> Self {
1870 match csp {
1871 Csp::Policy(policy) => {
1872 let mut map = HashMap::new();
1873 for directive in policy.split(';') {
1874 let mut tokens = directive.trim().split(' ');
1875 if let Some(directive) = tokens.next() {
1876 let sources = tokens.map(|s| s.to_string()).collect::<Vec<String>>();
1877 map.insert(directive.to_string(), CspDirectiveSources::List(sources));
1878 }
1879 }
1880 map
1881 }
1882 Csp::DirectiveMap(m) => m,
1883 }
1884 }
1885}
1886
1887impl Display for Csp {
1888 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1889 match self {
1890 Self::Policy(s) => write!(f, "{s}"),
1891 Self::DirectiveMap(m) => {
1892 let len = m.len();
1893 let mut i = 0;
1894 for (directive, sources) in m {
1895 let sources: Vec<String> = sources.clone().into();
1896 write!(f, "{} {}", directive, sources.join(" "))?;
1897 i += 1;
1898 if i != len {
1899 write!(f, "; ")?;
1900 }
1901 }
1902 Ok(())
1903 }
1904 }
1905 }
1906}
1907
1908#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1910#[serde(untagged)]
1911#[cfg_attr(feature = "schema", derive(JsonSchema))]
1912pub enum DisabledCspModificationKind {
1913 Flag(bool),
1916 List(Vec<String>),
1918}
1919
1920impl DisabledCspModificationKind {
1921 pub fn can_modify(&self, directive: &str) -> bool {
1923 match self {
1924 Self::Flag(f) => !f,
1925 Self::List(l) => !l.contains(&directive.into()),
1926 }
1927 }
1928}
1929
1930impl Default for DisabledCspModificationKind {
1931 fn default() -> Self {
1932 Self::Flag(false)
1933 }
1934}
1935
1936#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1945#[serde(untagged)]
1946#[cfg_attr(feature = "schema", derive(JsonSchema))]
1947pub enum FsScope {
1948 AllowedPaths(Vec<PathBuf>),
1950 #[serde(rename_all = "camelCase")]
1952 Scope {
1953 #[serde(default)]
1955 allow: Vec<PathBuf>,
1956 #[serde(default)]
1959 deny: Vec<PathBuf>,
1960 #[serde(alias = "require-literal-leading-dot")]
1969 require_literal_leading_dot: Option<bool>,
1970 },
1971}
1972
1973impl Default for FsScope {
1974 fn default() -> Self {
1975 Self::AllowedPaths(Vec::new())
1976 }
1977}
1978
1979impl FsScope {
1980 pub fn allowed_paths(&self) -> &Vec<PathBuf> {
1982 match self {
1983 Self::AllowedPaths(p) => p,
1984 Self::Scope { allow, .. } => allow,
1985 }
1986 }
1987
1988 pub fn forbidden_paths(&self) -> Option<&Vec<PathBuf>> {
1990 match self {
1991 Self::AllowedPaths(_) => None,
1992 Self::Scope { deny, .. } => Some(deny),
1993 }
1994 }
1995}
1996
1997#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2001#[cfg_attr(feature = "schema", derive(JsonSchema))]
2002#[serde(rename_all = "camelCase", deny_unknown_fields)]
2003pub struct AssetProtocolConfig {
2004 #[serde(default)]
2006 pub scope: FsScope,
2007 #[serde(default)]
2009 pub enable: bool,
2010}
2011
2012#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2016#[cfg_attr(feature = "schema", derive(JsonSchema))]
2017#[serde(rename_all = "camelCase", untagged)]
2018pub enum HeaderSource {
2019 Inline(String),
2021 List(Vec<String>),
2023 Map(HashMap<String, String>),
2025}
2026
2027impl Display for HeaderSource {
2028 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2029 match self {
2030 Self::Inline(s) => write!(f, "{s}"),
2031 Self::List(l) => write!(f, "{}", l.join(", ")),
2032 Self::Map(m) => {
2033 let len = m.len();
2034 let mut i = 0;
2035 for (key, value) in m {
2036 write!(f, "{} {}", key, value)?;
2037 i += 1;
2038 if i != len {
2039 write!(f, "; ")?;
2040 }
2041 }
2042 Ok(())
2043 }
2044 }
2045 }
2046}
2047
2048pub trait HeaderAddition {
2052 fn add_configured_headers(self, headers: Option<&HeaderConfig>) -> http::response::Builder;
2054}
2055
2056impl HeaderAddition for Builder {
2057 fn add_configured_headers(mut self, headers: Option<&HeaderConfig>) -> http::response::Builder {
2061 if let Some(headers) = headers {
2062 if let Some(value) = &headers.access_control_allow_credentials {
2064 self = self.header("Access-Control-Allow-Credentials", value.to_string());
2065 };
2066
2067 if let Some(value) = &headers.access_control_allow_headers {
2069 self = self.header("Access-Control-Allow-Headers", value.to_string());
2070 };
2071
2072 if let Some(value) = &headers.access_control_allow_methods {
2074 self = self.header("Access-Control-Allow-Methods", value.to_string());
2075 };
2076
2077 if let Some(value) = &headers.access_control_expose_headers {
2079 self = self.header("Access-Control-Expose-Headers", value.to_string());
2080 };
2081
2082 if let Some(value) = &headers.access_control_max_age {
2084 self = self.header("Access-Control-Max-Age", value.to_string());
2085 };
2086
2087 if let Some(value) = &headers.cross_origin_embedder_policy {
2089 self = self.header("Cross-Origin-Embedder-Policy", value.to_string());
2090 };
2091
2092 if let Some(value) = &headers.cross_origin_opener_policy {
2094 self = self.header("Cross-Origin-Opener-Policy", value.to_string());
2095 };
2096
2097 if let Some(value) = &headers.cross_origin_resource_policy {
2099 self = self.header("Cross-Origin-Resource-Policy", value.to_string());
2100 };
2101
2102 if let Some(value) = &headers.permissions_policy {
2104 self = self.header("Permission-Policy", value.to_string());
2105 };
2106
2107 if let Some(value) = &headers.timing_allow_origin {
2109 self = self.header("Timing-Allow-Origin", value.to_string());
2110 };
2111
2112 if let Some(value) = &headers.x_content_type_options {
2114 self = self.header("X-Content-Type-Options", value.to_string());
2115 };
2116
2117 if let Some(value) = &headers.tauri_custom_header {
2119 self = self.header("Tauri-Custom-Header", value.to_string());
2121 };
2122 }
2123 self
2124 }
2125}
2126
2127#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2178#[cfg_attr(feature = "schema", derive(JsonSchema))]
2179#[serde(deny_unknown_fields)]
2180pub struct HeaderConfig {
2181 #[serde(rename = "Access-Control-Allow-Credentials")]
2186 pub access_control_allow_credentials: Option<HeaderSource>,
2187 #[serde(rename = "Access-Control-Allow-Headers")]
2195 pub access_control_allow_headers: Option<HeaderSource>,
2196 #[serde(rename = "Access-Control-Allow-Methods")]
2201 pub access_control_allow_methods: Option<HeaderSource>,
2202 #[serde(rename = "Access-Control-Expose-Headers")]
2208 pub access_control_expose_headers: Option<HeaderSource>,
2209 #[serde(rename = "Access-Control-Max-Age")]
2216 pub access_control_max_age: Option<HeaderSource>,
2217 #[serde(rename = "Cross-Origin-Embedder-Policy")]
2222 pub cross_origin_embedder_policy: Option<HeaderSource>,
2223 #[serde(rename = "Cross-Origin-Opener-Policy")]
2230 pub cross_origin_opener_policy: Option<HeaderSource>,
2231 #[serde(rename = "Cross-Origin-Resource-Policy")]
2236 pub cross_origin_resource_policy: Option<HeaderSource>,
2237 #[serde(rename = "Permissions-Policy")]
2242 pub permissions_policy: Option<HeaderSource>,
2243 #[serde(rename = "Timing-Allow-Origin")]
2249 pub timing_allow_origin: Option<HeaderSource>,
2250 #[serde(rename = "X-Content-Type-Options")]
2257 pub x_content_type_options: Option<HeaderSource>,
2258 #[serde(rename = "Tauri-Custom-Header")]
2263 pub tauri_custom_header: Option<HeaderSource>,
2264}
2265
2266impl HeaderConfig {
2267 pub fn new() -> Self {
2269 HeaderConfig {
2270 access_control_allow_credentials: None,
2271 access_control_allow_methods: None,
2272 access_control_allow_headers: None,
2273 access_control_expose_headers: None,
2274 access_control_max_age: None,
2275 cross_origin_embedder_policy: None,
2276 cross_origin_opener_policy: None,
2277 cross_origin_resource_policy: None,
2278 permissions_policy: None,
2279 timing_allow_origin: None,
2280 x_content_type_options: None,
2281 tauri_custom_header: None,
2282 }
2283 }
2284}
2285
2286#[skip_serializing_none]
2290#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
2291#[cfg_attr(feature = "schema", derive(JsonSchema))]
2292#[serde(rename_all = "camelCase", deny_unknown_fields)]
2293pub struct SecurityConfig {
2294 pub csp: Option<Csp>,
2300 #[serde(alias = "dev-csp")]
2305 pub dev_csp: Option<Csp>,
2306 #[serde(default, alias = "freeze-prototype")]
2308 pub freeze_prototype: bool,
2309 #[serde(default, alias = "dangerous-disable-asset-csp-modification")]
2322 pub dangerous_disable_asset_csp_modification: DisabledCspModificationKind,
2323 #[serde(default, alias = "asset-protocol")]
2325 pub asset_protocol: AssetProtocolConfig,
2326 #[serde(default)]
2328 pub pattern: PatternKind,
2329 #[serde(default)]
2333 pub capabilities: Vec<CapabilityEntry>,
2334 #[serde(default)]
2337 pub headers: Option<HeaderConfig>,
2338}
2339
2340#[derive(Debug, Clone, PartialEq, Serialize)]
2342#[cfg_attr(feature = "schema", derive(JsonSchema))]
2343#[serde(untagged)]
2344pub enum CapabilityEntry {
2345 Inlined(Capability),
2347 Reference(String),
2349}
2350
2351impl<'de> Deserialize<'de> for CapabilityEntry {
2352 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2353 where
2354 D: Deserializer<'de>,
2355 {
2356 UntaggedEnumVisitor::new()
2357 .string(|string| Ok(Self::Reference(string.to_owned())))
2358 .map(|map| map.deserialize::<Capability>().map(Self::Inlined))
2359 .deserialize(deserializer)
2360 }
2361}
2362
2363#[skip_serializing_none]
2365#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
2366#[serde(rename_all = "lowercase", tag = "use", content = "options")]
2367#[cfg_attr(feature = "schema", derive(JsonSchema))]
2368pub enum PatternKind {
2369 Brownfield,
2371 Isolation {
2373 dir: PathBuf,
2375 },
2376}
2377
2378impl Default for PatternKind {
2379 fn default() -> Self {
2380 Self::Brownfield
2381 }
2382}
2383
2384#[skip_serializing_none]
2388#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
2389#[cfg_attr(feature = "schema", derive(JsonSchema))]
2390#[serde(rename_all = "camelCase", deny_unknown_fields)]
2391pub struct AppConfig {
2392 #[serde(default)]
2394 pub windows: Vec<WindowConfig>,
2395 #[serde(default)]
2397 pub security: SecurityConfig,
2398 #[serde(alias = "tray-icon")]
2400 pub tray_icon: Option<TrayIconConfig>,
2401 #[serde(rename = "macOSPrivateApi", alias = "macos-private-api", default)]
2403 pub macos_private_api: bool,
2404 #[serde(default, alias = "with-global-tauri")]
2406 pub with_global_tauri: bool,
2407 #[serde(rename = "enableGTKAppId", alias = "enable-gtk-app-id", default)]
2409 pub enable_gtk_app_id: bool,
2410}
2411
2412impl AppConfig {
2413 pub fn all_features() -> Vec<&'static str> {
2415 vec![
2416 "tray-icon",
2417 "macos-private-api",
2418 "protocol-asset",
2419 "isolation",
2420 ]
2421 }
2422
2423 pub fn features(&self) -> Vec<&str> {
2425 let mut features = Vec::new();
2426 if self.tray_icon.is_some() {
2427 features.push("tray-icon");
2428 }
2429 if self.macos_private_api {
2430 features.push("macos-private-api");
2431 }
2432 if self.security.asset_protocol.enable {
2433 features.push("protocol-asset");
2434 }
2435
2436 if let PatternKind::Isolation { .. } = self.security.pattern {
2437 features.push("isolation");
2438 }
2439
2440 features.sort_unstable();
2441 features
2442 }
2443}
2444
2445#[skip_serializing_none]
2449#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2450#[cfg_attr(feature = "schema", derive(JsonSchema))]
2451#[serde(rename_all = "camelCase", deny_unknown_fields)]
2452pub struct TrayIconConfig {
2453 pub id: Option<String>,
2455 #[serde(alias = "icon-path")]
2461 pub icon_path: PathBuf,
2462 #[serde(default, alias = "icon-as-template")]
2464 pub icon_as_template: bool,
2465 #[serde(default = "default_true", alias = "menu-on-left-click")]
2471 #[deprecated(since = "2.2.0", note = "Use `show_menu_on_left_click` instead.")]
2472 pub menu_on_left_click: bool,
2473 #[serde(default = "default_true", alias = "show-menu-on-left-click")]
2479 pub show_menu_on_left_click: bool,
2480 pub title: Option<String>,
2482 pub tooltip: Option<String>,
2484}
2485
2486#[skip_serializing_none]
2488#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2489#[cfg_attr(feature = "schema", derive(JsonSchema))]
2490#[serde(rename_all = "camelCase", deny_unknown_fields)]
2491pub struct IosConfig {
2492 pub template: Option<PathBuf>,
2496 pub frameworks: Option<Vec<String>>,
2500 #[serde(alias = "development-team")]
2503 pub development_team: Option<String>,
2504 #[serde(
2508 alias = "minimum-system-version",
2509 default = "ios_minimum_system_version"
2510 )]
2511 pub minimum_system_version: String,
2512}
2513
2514impl Default for IosConfig {
2515 fn default() -> Self {
2516 Self {
2517 template: None,
2518 frameworks: None,
2519 development_team: None,
2520 minimum_system_version: ios_minimum_system_version(),
2521 }
2522 }
2523}
2524
2525#[skip_serializing_none]
2527#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2528#[cfg_attr(feature = "schema", derive(JsonSchema))]
2529#[serde(rename_all = "camelCase", deny_unknown_fields)]
2530pub struct AndroidConfig {
2531 #[serde(alias = "min-sdk-version", default = "default_min_sdk_version")]
2534 pub min_sdk_version: u32,
2535
2536 #[serde(alias = "version-code")]
2542 #[cfg_attr(feature = "schema", validate(range(min = 1, max = 2_100_000_000)))]
2543 pub version_code: Option<u32>,
2544}
2545
2546impl Default for AndroidConfig {
2547 fn default() -> Self {
2548 Self {
2549 min_sdk_version: default_min_sdk_version(),
2550 version_code: None,
2551 }
2552 }
2553}
2554
2555fn default_min_sdk_version() -> u32 {
2556 24
2557}
2558
2559#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2561#[cfg_attr(feature = "schema", derive(JsonSchema))]
2562#[serde(untagged, deny_unknown_fields)]
2563#[non_exhaustive]
2564pub enum FrontendDist {
2565 Url(Url),
2567 Directory(PathBuf),
2569 Files(Vec<PathBuf>),
2571}
2572
2573impl std::fmt::Display for FrontendDist {
2574 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2575 match self {
2576 Self::Url(url) => write!(f, "{url}"),
2577 Self::Directory(p) => write!(f, "{}", p.display()),
2578 Self::Files(files) => write!(f, "{}", serde_json::to_string(files).unwrap()),
2579 }
2580 }
2581}
2582
2583#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2585#[cfg_attr(feature = "schema", derive(JsonSchema))]
2586#[serde(rename_all = "camelCase", untagged)]
2587pub enum BeforeDevCommand {
2588 Script(String),
2590 ScriptWithOptions {
2592 script: String,
2594 cwd: Option<String>,
2596 #[serde(default)]
2598 wait: bool,
2599 },
2600}
2601
2602#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2604#[cfg_attr(feature = "schema", derive(JsonSchema))]
2605#[serde(rename_all = "camelCase", untagged)]
2606pub enum HookCommand {
2607 Script(String),
2609 ScriptWithOptions {
2611 script: String,
2613 cwd: Option<String>,
2615 },
2616}
2617
2618#[skip_serializing_none]
2622#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Default)]
2623#[cfg_attr(feature = "schema", derive(JsonSchema))]
2624#[serde(rename_all = "camelCase", deny_unknown_fields)]
2625pub struct BuildConfig {
2626 pub runner: Option<String>,
2628 #[serde(alias = "dev-url")]
2636 pub dev_url: Option<Url>,
2637 #[serde(alias = "frontend-dist")]
2651 pub frontend_dist: Option<FrontendDist>,
2652 #[serde(alias = "before-dev-command")]
2656 pub before_dev_command: Option<BeforeDevCommand>,
2657 #[serde(alias = "before-build-command")]
2661 pub before_build_command: Option<HookCommand>,
2662 #[serde(alias = "before-bundle-command")]
2666 pub before_bundle_command: Option<HookCommand>,
2667 pub features: Option<Vec<String>>,
2669}
2670
2671#[derive(Debug, PartialEq, Eq)]
2672struct PackageVersion(String);
2673
2674impl<'d> serde::Deserialize<'d> for PackageVersion {
2675 fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {
2676 struct PackageVersionVisitor;
2677
2678 impl Visitor<'_> for PackageVersionVisitor {
2679 type Value = PackageVersion;
2680
2681 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2682 write!(
2683 formatter,
2684 "a semver string or a path to a package.json file"
2685 )
2686 }
2687
2688 fn visit_str<E: DeError>(self, value: &str) -> Result<PackageVersion, E> {
2689 let path = PathBuf::from(value);
2690 if path.exists() {
2691 let json_str = read_to_string(&path)
2692 .map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?;
2693 let package_json: serde_json::Value = serde_json::from_str(&json_str)
2694 .map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?;
2695 if let Some(obj) = package_json.as_object() {
2696 let version = obj
2697 .get("version")
2698 .ok_or_else(|| DeError::custom("JSON must contain a `version` field"))?
2699 .as_str()
2700 .ok_or_else(|| {
2701 DeError::custom(format!("`{} > version` must be a string", path.display()))
2702 })?;
2703 Ok(PackageVersion(
2704 Version::from_str(version)
2705 .map_err(|_| DeError::custom("`package > version` must be a semver string"))?
2706 .to_string(),
2707 ))
2708 } else {
2709 Err(DeError::custom(
2710 "`package > version` value is not a path to a JSON object",
2711 ))
2712 }
2713 } else {
2714 Ok(PackageVersion(
2715 Version::from_str(value)
2716 .map_err(|_| DeError::custom("`package > version` must be a semver string"))?
2717 .to_string(),
2718 ))
2719 }
2720 }
2721 }
2722
2723 deserializer.deserialize_string(PackageVersionVisitor {})
2724 }
2725}
2726
2727fn version_deserializer<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
2728where
2729 D: Deserializer<'de>,
2730{
2731 Option::<PackageVersion>::deserialize(deserializer).map(|v| v.map(|v| v.0))
2732}
2733
2734#[skip_serializing_none]
2800#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
2801#[cfg_attr(feature = "schema", derive(JsonSchema))]
2802#[serde(rename_all = "camelCase", deny_unknown_fields)]
2803pub struct Config {
2804 #[serde(rename = "$schema")]
2806 pub schema: Option<String>,
2807 #[serde(alias = "product-name")]
2809 #[cfg_attr(feature = "schema", validate(regex(pattern = "^[^/\\:*?\"<>|]+$")))]
2810 pub product_name: Option<String>,
2811 #[serde(alias = "main-binary-name")]
2813 pub main_binary_name: Option<String>,
2814 #[serde(deserialize_with = "version_deserializer", default)]
2818 pub version: Option<String>,
2819 pub identifier: String,
2825 #[serde(default)]
2827 pub app: AppConfig,
2828 #[serde(default = "default_build")]
2830 pub build: BuildConfig,
2831 #[serde(default)]
2833 pub bundle: BundleConfig,
2834 #[serde(default)]
2836 pub plugins: PluginConfig,
2837}
2838
2839#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
2843#[cfg_attr(feature = "schema", derive(JsonSchema))]
2844pub struct PluginConfig(pub HashMap<String, JsonValue>);
2845
2846fn default_build() -> BuildConfig {
2847 BuildConfig {
2848 runner: None,
2849 dev_url: None,
2850 frontend_dist: None,
2851 before_dev_command: None,
2852 before_build_command: None,
2853 before_bundle_command: None,
2854 features: None,
2855 }
2856}
2857
2858#[cfg(feature = "build")]
2864mod build {
2865 use super::*;
2866 use crate::{literal_struct, tokens::*};
2867 use proc_macro2::TokenStream;
2868 use quote::{quote, ToTokens, TokenStreamExt};
2869 use std::convert::identity;
2870
2871 impl ToTokens for WebviewUrl {
2872 fn to_tokens(&self, tokens: &mut TokenStream) {
2873 let prefix = quote! { ::tauri::utils::config::WebviewUrl };
2874
2875 tokens.append_all(match self {
2876 Self::App(path) => {
2877 let path = path_buf_lit(path);
2878 quote! { #prefix::App(#path) }
2879 }
2880 Self::External(url) => {
2881 let url = url_lit(url);
2882 quote! { #prefix::External(#url) }
2883 }
2884 Self::CustomProtocol(url) => {
2885 let url = url_lit(url);
2886 quote! { #prefix::CustomProtocol(#url) }
2887 }
2888 })
2889 }
2890 }
2891
2892 impl ToTokens for BackgroundThrottlingPolicy {
2893 fn to_tokens(&self, tokens: &mut TokenStream) {
2894 let prefix = quote! { ::tauri::utils::config::BackgroundThrottlingPolicy };
2895 tokens.append_all(match self {
2896 Self::Disabled => quote! { #prefix::Disabled },
2897 Self::Throttle => quote! { #prefix::Throttle },
2898 Self::Suspend => quote! { #prefix::Suspend },
2899 })
2900 }
2901 }
2902
2903 impl ToTokens for crate::Theme {
2904 fn to_tokens(&self, tokens: &mut TokenStream) {
2905 let prefix = quote! { ::tauri::utils::Theme };
2906
2907 tokens.append_all(match self {
2908 Self::Light => quote! { #prefix::Light },
2909 Self::Dark => quote! { #prefix::Dark },
2910 })
2911 }
2912 }
2913
2914 impl ToTokens for Color {
2915 fn to_tokens(&self, tokens: &mut TokenStream) {
2916 let Color(r, g, b, a) = self;
2917 tokens.append_all(quote! {::tauri::utils::config::Color(#r,#g,#b,#a)});
2918 }
2919 }
2920 impl ToTokens for WindowEffectsConfig {
2921 fn to_tokens(&self, tokens: &mut TokenStream) {
2922 let effects = vec_lit(self.effects.clone(), |d| d);
2923 let state = opt_lit(self.state.as_ref());
2924 let radius = opt_lit(self.radius.as_ref());
2925 let color = opt_lit(self.color.as_ref());
2926
2927 literal_struct!(
2928 tokens,
2929 ::tauri::utils::config::WindowEffectsConfig,
2930 effects,
2931 state,
2932 radius,
2933 color
2934 )
2935 }
2936 }
2937
2938 impl ToTokens for crate::TitleBarStyle {
2939 fn to_tokens(&self, tokens: &mut TokenStream) {
2940 let prefix = quote! { ::tauri::utils::TitleBarStyle };
2941
2942 tokens.append_all(match self {
2943 Self::Visible => quote! { #prefix::Visible },
2944 Self::Transparent => quote! { #prefix::Transparent },
2945 Self::Overlay => quote! { #prefix::Overlay },
2946 })
2947 }
2948 }
2949
2950 impl ToTokens for crate::WindowEffect {
2951 fn to_tokens(&self, tokens: &mut TokenStream) {
2952 let prefix = quote! { ::tauri::utils::WindowEffect };
2953
2954 #[allow(deprecated)]
2955 tokens.append_all(match self {
2956 WindowEffect::AppearanceBased => quote! { #prefix::AppearanceBased},
2957 WindowEffect::Light => quote! { #prefix::Light},
2958 WindowEffect::Dark => quote! { #prefix::Dark},
2959 WindowEffect::MediumLight => quote! { #prefix::MediumLight},
2960 WindowEffect::UltraDark => quote! { #prefix::UltraDark},
2961 WindowEffect::Titlebar => quote! { #prefix::Titlebar},
2962 WindowEffect::Selection => quote! { #prefix::Selection},
2963 WindowEffect::Menu => quote! { #prefix::Menu},
2964 WindowEffect::Popover => quote! { #prefix::Popover},
2965 WindowEffect::Sidebar => quote! { #prefix::Sidebar},
2966 WindowEffect::HeaderView => quote! { #prefix::HeaderView},
2967 WindowEffect::Sheet => quote! { #prefix::Sheet},
2968 WindowEffect::WindowBackground => quote! { #prefix::WindowBackground},
2969 WindowEffect::HudWindow => quote! { #prefix::HudWindow},
2970 WindowEffect::FullScreenUI => quote! { #prefix::FullScreenUI},
2971 WindowEffect::Tooltip => quote! { #prefix::Tooltip},
2972 WindowEffect::ContentBackground => quote! { #prefix::ContentBackground},
2973 WindowEffect::UnderWindowBackground => quote! { #prefix::UnderWindowBackground},
2974 WindowEffect::UnderPageBackground => quote! { #prefix::UnderPageBackground},
2975 WindowEffect::Mica => quote! { #prefix::Mica},
2976 WindowEffect::MicaDark => quote! { #prefix::MicaDark},
2977 WindowEffect::MicaLight => quote! { #prefix::MicaLight},
2978 WindowEffect::Blur => quote! { #prefix::Blur},
2979 WindowEffect::Acrylic => quote! { #prefix::Acrylic},
2980 WindowEffect::Tabbed => quote! { #prefix::Tabbed },
2981 WindowEffect::TabbedDark => quote! { #prefix::TabbedDark },
2982 WindowEffect::TabbedLight => quote! { #prefix::TabbedLight },
2983 })
2984 }
2985 }
2986
2987 impl ToTokens for crate::WindowEffectState {
2988 fn to_tokens(&self, tokens: &mut TokenStream) {
2989 let prefix = quote! { ::tauri::utils::WindowEffectState };
2990
2991 #[allow(deprecated)]
2992 tokens.append_all(match self {
2993 WindowEffectState::Active => quote! { #prefix::Active},
2994 WindowEffectState::FollowsWindowActiveState => quote! { #prefix::FollowsWindowActiveState},
2995 WindowEffectState::Inactive => quote! { #prefix::Inactive},
2996 })
2997 }
2998 }
2999
3000 impl ToTokens for WindowConfig {
3001 fn to_tokens(&self, tokens: &mut TokenStream) {
3002 let label = str_lit(&self.label);
3003 let create = &self.create;
3004 let url = &self.url;
3005 let user_agent = opt_str_lit(self.user_agent.as_ref());
3006 let drag_drop_enabled = self.drag_drop_enabled;
3007 let center = self.center;
3008 let x = opt_lit(self.x.as_ref());
3009 let y = opt_lit(self.y.as_ref());
3010 let width = self.width;
3011 let height = self.height;
3012 let min_width = opt_lit(self.min_width.as_ref());
3013 let min_height = opt_lit(self.min_height.as_ref());
3014 let max_width = opt_lit(self.max_width.as_ref());
3015 let max_height = opt_lit(self.max_height.as_ref());
3016 let resizable = self.resizable;
3017 let maximizable = self.maximizable;
3018 let minimizable = self.minimizable;
3019 let closable = self.closable;
3020 let title = str_lit(&self.title);
3021 let proxy_url = opt_lit(self.proxy_url.as_ref().map(url_lit).as_ref());
3022 let fullscreen = self.fullscreen;
3023 let focus = self.focus;
3024 let transparent = self.transparent;
3025 let maximized = self.maximized;
3026 let visible = self.visible;
3027 let decorations = self.decorations;
3028 let always_on_bottom = self.always_on_bottom;
3029 let always_on_top = self.always_on_top;
3030 let visible_on_all_workspaces = self.visible_on_all_workspaces;
3031 let content_protected = self.content_protected;
3032 let skip_taskbar = self.skip_taskbar;
3033 let window_classname = opt_str_lit(self.window_classname.as_ref());
3034 let theme = opt_lit(self.theme.as_ref());
3035 let title_bar_style = &self.title_bar_style;
3036 let hidden_title = self.hidden_title;
3037 let accept_first_mouse = self.accept_first_mouse;
3038 let tabbing_identifier = opt_str_lit(self.tabbing_identifier.as_ref());
3039 let additional_browser_args = opt_str_lit(self.additional_browser_args.as_ref());
3040 let shadow = self.shadow;
3041 let window_effects = opt_lit(self.window_effects.as_ref());
3042 let incognito = self.incognito;
3043 let parent = opt_str_lit(self.parent.as_ref());
3044 let zoom_hotkeys_enabled = self.zoom_hotkeys_enabled;
3045 let browser_extensions_enabled = self.browser_extensions_enabled;
3046 let use_https_scheme = self.use_https_scheme;
3047 let devtools = opt_lit(self.devtools.as_ref());
3048 let background_color = opt_lit(self.background_color.as_ref());
3049 let background_throttling = opt_lit(self.background_throttling.as_ref());
3050
3051 literal_struct!(
3052 tokens,
3053 ::tauri::utils::config::WindowConfig,
3054 label,
3055 url,
3056 create,
3057 user_agent,
3058 drag_drop_enabled,
3059 center,
3060 x,
3061 y,
3062 width,
3063 height,
3064 min_width,
3065 min_height,
3066 max_width,
3067 max_height,
3068 resizable,
3069 maximizable,
3070 minimizable,
3071 closable,
3072 title,
3073 proxy_url,
3074 fullscreen,
3075 focus,
3076 transparent,
3077 maximized,
3078 visible,
3079 decorations,
3080 always_on_bottom,
3081 always_on_top,
3082 visible_on_all_workspaces,
3083 content_protected,
3084 skip_taskbar,
3085 window_classname,
3086 theme,
3087 title_bar_style,
3088 hidden_title,
3089 accept_first_mouse,
3090 tabbing_identifier,
3091 additional_browser_args,
3092 shadow,
3093 window_effects,
3094 incognito,
3095 parent,
3096 zoom_hotkeys_enabled,
3097 browser_extensions_enabled,
3098 use_https_scheme,
3099 devtools,
3100 background_color,
3101 background_throttling
3102 );
3103 }
3104 }
3105
3106 impl ToTokens for PatternKind {
3107 fn to_tokens(&self, tokens: &mut TokenStream) {
3108 let prefix = quote! { ::tauri::utils::config::PatternKind };
3109
3110 tokens.append_all(match self {
3111 Self::Brownfield => quote! { #prefix::Brownfield },
3112 #[cfg(not(feature = "isolation"))]
3113 Self::Isolation { dir: _ } => quote! { #prefix::Brownfield },
3114 #[cfg(feature = "isolation")]
3115 Self::Isolation { dir } => {
3116 let dir = path_buf_lit(dir);
3117 quote! { #prefix::Isolation { dir: #dir } }
3118 }
3119 })
3120 }
3121 }
3122
3123 impl ToTokens for WebviewInstallMode {
3124 fn to_tokens(&self, tokens: &mut TokenStream) {
3125 let prefix = quote! { ::tauri::utils::config::WebviewInstallMode };
3126
3127 tokens.append_all(match self {
3128 Self::Skip => quote! { #prefix::Skip },
3129 Self::DownloadBootstrapper { silent } => {
3130 quote! { #prefix::DownloadBootstrapper { silent: #silent } }
3131 }
3132 Self::EmbedBootstrapper { silent } => {
3133 quote! { #prefix::EmbedBootstrapper { silent: #silent } }
3134 }
3135 Self::OfflineInstaller { silent } => {
3136 quote! { #prefix::OfflineInstaller { silent: #silent } }
3137 }
3138 Self::FixedRuntime { path } => {
3139 let path = path_buf_lit(path);
3140 quote! { #prefix::FixedRuntime { path: #path } }
3141 }
3142 })
3143 }
3144 }
3145
3146 impl ToTokens for WindowsConfig {
3147 fn to_tokens(&self, tokens: &mut TokenStream) {
3148 let webview_install_mode = &self.webview_install_mode;
3149 tokens.append_all(quote! { ::tauri::utils::config::WindowsConfig {
3150 webview_install_mode: #webview_install_mode,
3151 ..Default::default()
3152 }})
3153 }
3154 }
3155
3156 impl ToTokens for BundleConfig {
3157 fn to_tokens(&self, tokens: &mut TokenStream) {
3158 let publisher = quote!(None);
3159 let homepage = quote!(None);
3160 let icon = vec_lit(&self.icon, str_lit);
3161 let active = self.active;
3162 let targets = quote!(Default::default());
3163 let create_updater_artifacts = quote!(Default::default());
3164 let resources = quote!(None);
3165 let copyright = quote!(None);
3166 let category = quote!(None);
3167 let file_associations = quote!(None);
3168 let short_description = quote!(None);
3169 let long_description = quote!(None);
3170 let use_local_tools_dir = self.use_local_tools_dir;
3171 let external_bin = opt_vec_lit(self.external_bin.as_ref(), str_lit);
3172 let windows = &self.windows;
3173 let license = opt_str_lit(self.license.as_ref());
3174 let license_file = opt_lit(self.license_file.as_ref().map(path_buf_lit).as_ref());
3175 let linux = quote!(Default::default());
3176 let macos = quote!(Default::default());
3177 let ios = quote!(Default::default());
3178 let android = quote!(Default::default());
3179
3180 literal_struct!(
3181 tokens,
3182 ::tauri::utils::config::BundleConfig,
3183 active,
3184 publisher,
3185 homepage,
3186 icon,
3187 targets,
3188 create_updater_artifacts,
3189 resources,
3190 copyright,
3191 category,
3192 license,
3193 license_file,
3194 file_associations,
3195 short_description,
3196 long_description,
3197 use_local_tools_dir,
3198 external_bin,
3199 windows,
3200 linux,
3201 macos,
3202 ios,
3203 android
3204 );
3205 }
3206 }
3207
3208 impl ToTokens for FrontendDist {
3209 fn to_tokens(&self, tokens: &mut TokenStream) {
3210 let prefix = quote! { ::tauri::utils::config::FrontendDist };
3211
3212 tokens.append_all(match self {
3213 Self::Url(url) => {
3214 let url = url_lit(url);
3215 quote! { #prefix::Url(#url) }
3216 }
3217 Self::Directory(path) => {
3218 let path = path_buf_lit(path);
3219 quote! { #prefix::Directory(#path) }
3220 }
3221 Self::Files(files) => {
3222 let files = vec_lit(files, path_buf_lit);
3223 quote! { #prefix::Files(#files) }
3224 }
3225 })
3226 }
3227 }
3228
3229 impl ToTokens for BuildConfig {
3230 fn to_tokens(&self, tokens: &mut TokenStream) {
3231 let dev_url = opt_lit(self.dev_url.as_ref().map(url_lit).as_ref());
3232 let frontend_dist = opt_lit(self.frontend_dist.as_ref());
3233 let runner = quote!(None);
3234 let before_dev_command = quote!(None);
3235 let before_build_command = quote!(None);
3236 let before_bundle_command = quote!(None);
3237 let features = quote!(None);
3238
3239 literal_struct!(
3240 tokens,
3241 ::tauri::utils::config::BuildConfig,
3242 runner,
3243 dev_url,
3244 frontend_dist,
3245 before_dev_command,
3246 before_build_command,
3247 before_bundle_command,
3248 features
3249 );
3250 }
3251 }
3252
3253 impl ToTokens for CspDirectiveSources {
3254 fn to_tokens(&self, tokens: &mut TokenStream) {
3255 let prefix = quote! { ::tauri::utils::config::CspDirectiveSources };
3256
3257 tokens.append_all(match self {
3258 Self::Inline(sources) => {
3259 let sources = sources.as_str();
3260 quote!(#prefix::Inline(#sources.into()))
3261 }
3262 Self::List(list) => {
3263 let list = vec_lit(list, str_lit);
3264 quote!(#prefix::List(#list))
3265 }
3266 })
3267 }
3268 }
3269
3270 impl ToTokens for Csp {
3271 fn to_tokens(&self, tokens: &mut TokenStream) {
3272 let prefix = quote! { ::tauri::utils::config::Csp };
3273
3274 tokens.append_all(match self {
3275 Self::Policy(policy) => {
3276 let policy = policy.as_str();
3277 quote!(#prefix::Policy(#policy.into()))
3278 }
3279 Self::DirectiveMap(list) => {
3280 let map = map_lit(
3281 quote! { ::std::collections::HashMap },
3282 list,
3283 str_lit,
3284 identity,
3285 );
3286 quote!(#prefix::DirectiveMap(#map))
3287 }
3288 })
3289 }
3290 }
3291
3292 impl ToTokens for DisabledCspModificationKind {
3293 fn to_tokens(&self, tokens: &mut TokenStream) {
3294 let prefix = quote! { ::tauri::utils::config::DisabledCspModificationKind };
3295
3296 tokens.append_all(match self {
3297 Self::Flag(flag) => {
3298 quote! { #prefix::Flag(#flag) }
3299 }
3300 Self::List(directives) => {
3301 let directives = vec_lit(directives, str_lit);
3302 quote! { #prefix::List(#directives) }
3303 }
3304 });
3305 }
3306 }
3307
3308 impl ToTokens for CapabilityEntry {
3309 fn to_tokens(&self, tokens: &mut TokenStream) {
3310 let prefix = quote! { ::tauri::utils::config::CapabilityEntry };
3311
3312 tokens.append_all(match self {
3313 Self::Inlined(capability) => {
3314 quote! { #prefix::Inlined(#capability) }
3315 }
3316 Self::Reference(id) => {
3317 let id = str_lit(id);
3318 quote! { #prefix::Reference(#id) }
3319 }
3320 });
3321 }
3322 }
3323
3324 impl ToTokens for HeaderSource {
3325 fn to_tokens(&self, tokens: &mut TokenStream) {
3326 let prefix = quote! { ::tauri::utils::config::HeaderSource };
3327
3328 tokens.append_all(match self {
3329 Self::Inline(s) => {
3330 let line = s.as_str();
3331 quote!(#prefix::Inline(#line.into()))
3332 }
3333 Self::List(l) => {
3334 let list = vec_lit(l, str_lit);
3335 quote!(#prefix::List(#list))
3336 }
3337 Self::Map(m) => {
3338 let map = map_lit(quote! { ::std::collections::HashMap }, m, str_lit, str_lit);
3339 quote!(#prefix::Map(#map))
3340 }
3341 })
3342 }
3343 }
3344
3345 impl ToTokens for HeaderConfig {
3346 fn to_tokens(&self, tokens: &mut TokenStream) {
3347 let access_control_allow_credentials =
3348 opt_lit(self.access_control_allow_credentials.as_ref());
3349 let access_control_allow_headers = opt_lit(self.access_control_allow_headers.as_ref());
3350 let access_control_allow_methods = opt_lit(self.access_control_allow_methods.as_ref());
3351 let access_control_expose_headers = opt_lit(self.access_control_expose_headers.as_ref());
3352 let access_control_max_age = opt_lit(self.access_control_max_age.as_ref());
3353 let cross_origin_embedder_policy = opt_lit(self.cross_origin_embedder_policy.as_ref());
3354 let cross_origin_opener_policy = opt_lit(self.cross_origin_opener_policy.as_ref());
3355 let cross_origin_resource_policy = opt_lit(self.cross_origin_resource_policy.as_ref());
3356 let permissions_policy = opt_lit(self.permissions_policy.as_ref());
3357 let timing_allow_origin = opt_lit(self.timing_allow_origin.as_ref());
3358 let x_content_type_options = opt_lit(self.x_content_type_options.as_ref());
3359 let tauri_custom_header = opt_lit(self.tauri_custom_header.as_ref());
3360
3361 literal_struct!(
3362 tokens,
3363 ::tauri::utils::config::HeaderConfig,
3364 access_control_allow_credentials,
3365 access_control_allow_headers,
3366 access_control_allow_methods,
3367 access_control_expose_headers,
3368 access_control_max_age,
3369 cross_origin_embedder_policy,
3370 cross_origin_opener_policy,
3371 cross_origin_resource_policy,
3372 permissions_policy,
3373 timing_allow_origin,
3374 x_content_type_options,
3375 tauri_custom_header
3376 );
3377 }
3378 }
3379
3380 impl ToTokens for SecurityConfig {
3381 fn to_tokens(&self, tokens: &mut TokenStream) {
3382 let csp = opt_lit(self.csp.as_ref());
3383 let dev_csp = opt_lit(self.dev_csp.as_ref());
3384 let freeze_prototype = self.freeze_prototype;
3385 let dangerous_disable_asset_csp_modification = &self.dangerous_disable_asset_csp_modification;
3386 let asset_protocol = &self.asset_protocol;
3387 let pattern = &self.pattern;
3388 let capabilities = vec_lit(&self.capabilities, identity);
3389 let headers = opt_lit(self.headers.as_ref());
3390
3391 literal_struct!(
3392 tokens,
3393 ::tauri::utils::config::SecurityConfig,
3394 csp,
3395 dev_csp,
3396 freeze_prototype,
3397 dangerous_disable_asset_csp_modification,
3398 asset_protocol,
3399 pattern,
3400 capabilities,
3401 headers
3402 );
3403 }
3404 }
3405
3406 impl ToTokens for TrayIconConfig {
3407 fn to_tokens(&self, tokens: &mut TokenStream) {
3408 let id = opt_str_lit(self.id.as_ref());
3409 let icon_as_template = self.icon_as_template;
3410 #[allow(deprecated)]
3411 let menu_on_left_click = self.menu_on_left_click;
3412 let show_menu_on_left_click = self.show_menu_on_left_click;
3413 let icon_path = path_buf_lit(&self.icon_path);
3414 let title = opt_str_lit(self.title.as_ref());
3415 let tooltip = opt_str_lit(self.tooltip.as_ref());
3416 literal_struct!(
3417 tokens,
3418 ::tauri::utils::config::TrayIconConfig,
3419 id,
3420 icon_path,
3421 icon_as_template,
3422 menu_on_left_click,
3423 show_menu_on_left_click,
3424 title,
3425 tooltip
3426 );
3427 }
3428 }
3429
3430 impl ToTokens for FsScope {
3431 fn to_tokens(&self, tokens: &mut TokenStream) {
3432 let prefix = quote! { ::tauri::utils::config::FsScope };
3433
3434 tokens.append_all(match self {
3435 Self::AllowedPaths(allow) => {
3436 let allowed_paths = vec_lit(allow, path_buf_lit);
3437 quote! { #prefix::AllowedPaths(#allowed_paths) }
3438 }
3439 Self::Scope { allow, deny , require_literal_leading_dot} => {
3440 let allow = vec_lit(allow, path_buf_lit);
3441 let deny = vec_lit(deny, path_buf_lit);
3442 let require_literal_leading_dot = opt_lit(require_literal_leading_dot.as_ref());
3443 quote! { #prefix::Scope { allow: #allow, deny: #deny, require_literal_leading_dot: #require_literal_leading_dot } }
3444 }
3445 });
3446 }
3447 }
3448
3449 impl ToTokens for AssetProtocolConfig {
3450 fn to_tokens(&self, tokens: &mut TokenStream) {
3451 let scope = &self.scope;
3452 tokens.append_all(quote! { ::tauri::utils::config::AssetProtocolConfig { scope: #scope, ..Default::default() } })
3453 }
3454 }
3455
3456 impl ToTokens for AppConfig {
3457 fn to_tokens(&self, tokens: &mut TokenStream) {
3458 let windows = vec_lit(&self.windows, identity);
3459 let security = &self.security;
3460 let tray_icon = opt_lit(self.tray_icon.as_ref());
3461 let macos_private_api = self.macos_private_api;
3462 let with_global_tauri = self.with_global_tauri;
3463 let enable_gtk_app_id = self.enable_gtk_app_id;
3464
3465 literal_struct!(
3466 tokens,
3467 ::tauri::utils::config::AppConfig,
3468 windows,
3469 security,
3470 tray_icon,
3471 macos_private_api,
3472 with_global_tauri,
3473 enable_gtk_app_id
3474 );
3475 }
3476 }
3477
3478 impl ToTokens for PluginConfig {
3479 fn to_tokens(&self, tokens: &mut TokenStream) {
3480 let config = map_lit(
3481 quote! { ::std::collections::HashMap },
3482 &self.0,
3483 str_lit,
3484 json_value_lit,
3485 );
3486 tokens.append_all(quote! { ::tauri::utils::config::PluginConfig(#config) })
3487 }
3488 }
3489
3490 impl ToTokens for Config {
3491 fn to_tokens(&self, tokens: &mut TokenStream) {
3492 let schema = quote!(None);
3493 let product_name = opt_str_lit(self.product_name.as_ref());
3494 let main_binary_name = opt_str_lit(self.main_binary_name.as_ref());
3495 let version = opt_str_lit(self.version.as_ref());
3496 let identifier = str_lit(&self.identifier);
3497 let app = &self.app;
3498 let build = &self.build;
3499 let bundle = &self.bundle;
3500 let plugins = &self.plugins;
3501
3502 literal_struct!(
3503 tokens,
3504 ::tauri::utils::config::Config,
3505 schema,
3506 product_name,
3507 main_binary_name,
3508 version,
3509 identifier,
3510 app,
3511 build,
3512 bundle,
3513 plugins
3514 );
3515 }
3516 }
3517}
3518
3519#[cfg(test)]
3520mod test {
3521 use super::*;
3522
3523 #[test]
3526 fn test_defaults() {
3528 let a_config = AppConfig::default();
3530 let b_config = BuildConfig::default();
3532 let d_windows: Vec<WindowConfig> = vec![];
3534 let d_bundle = BundleConfig::default();
3536
3537 let app = AppConfig {
3539 windows: vec![],
3540 security: SecurityConfig {
3541 csp: None,
3542 dev_csp: None,
3543 freeze_prototype: false,
3544 dangerous_disable_asset_csp_modification: DisabledCspModificationKind::Flag(false),
3545 asset_protocol: AssetProtocolConfig::default(),
3546 pattern: Default::default(),
3547 capabilities: Vec::new(),
3548 headers: None,
3549 },
3550 tray_icon: None,
3551 macos_private_api: false,
3552 with_global_tauri: false,
3553 enable_gtk_app_id: false,
3554 };
3555
3556 let build = BuildConfig {
3558 runner: None,
3559 dev_url: None,
3560 frontend_dist: None,
3561 before_dev_command: None,
3562 before_build_command: None,
3563 before_bundle_command: None,
3564 features: None,
3565 };
3566
3567 let bundle = BundleConfig {
3569 active: false,
3570 targets: Default::default(),
3571 create_updater_artifacts: Default::default(),
3572 publisher: None,
3573 homepage: None,
3574 icon: Vec::new(),
3575 resources: None,
3576 copyright: None,
3577 category: None,
3578 file_associations: None,
3579 short_description: None,
3580 long_description: None,
3581 use_local_tools_dir: false,
3582 license: None,
3583 license_file: None,
3584 linux: Default::default(),
3585 macos: Default::default(),
3586 external_bin: None,
3587 windows: Default::default(),
3588 ios: Default::default(),
3589 android: Default::default(),
3590 };
3591
3592 assert_eq!(a_config, app);
3594 assert_eq!(b_config, build);
3595 assert_eq!(d_bundle, bundle);
3596 assert_eq!(d_windows, app.windows);
3597 }
3598
3599 #[test]
3600 fn parse_hex_color() {
3601 use super::Color;
3602
3603 assert_eq!(Color(255, 255, 255, 255), "fff".parse().unwrap());
3604 assert_eq!(Color(255, 255, 255, 255), "#fff".parse().unwrap());
3605 assert_eq!(Color(0, 0, 0, 255), "#000000".parse().unwrap());
3606 assert_eq!(Color(0, 0, 0, 255), "#000000ff".parse().unwrap());
3607 assert_eq!(Color(0, 255, 0, 255), "#00ff00ff".parse().unwrap());
3608 }
3609}