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#[skip_serializing_none]
1425#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default)]
1426#[cfg_attr(feature = "schema", derive(JsonSchema))]
1427#[serde(rename_all = "camelCase", deny_unknown_fields)]
1428pub struct WindowEffectsConfig {
1429 pub effects: Vec<WindowEffect>,
1432 pub state: Option<WindowEffectState>,
1434 pub radius: Option<f64>,
1436 pub color: Option<Color>,
1439}
1440
1441#[skip_serializing_none]
1445#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
1446#[cfg_attr(feature = "schema", derive(JsonSchema))]
1447#[serde(rename_all = "camelCase", deny_unknown_fields)]
1448pub struct WindowConfig {
1449 #[serde(default = "default_window_label")]
1451 pub label: String,
1452 #[serde(default = "default_true")]
1457 pub create: bool,
1458 #[serde(default)]
1460 pub url: WebviewUrl,
1461 #[serde(alias = "user-agent")]
1463 pub user_agent: Option<String>,
1464 #[serde(default = "default_true", alias = "drag-drop-enabled")]
1468 pub drag_drop_enabled: bool,
1469 #[serde(default)]
1471 pub center: bool,
1472 pub x: Option<f64>,
1474 pub y: Option<f64>,
1476 #[serde(default = "default_width")]
1478 pub width: f64,
1479 #[serde(default = "default_height")]
1481 pub height: f64,
1482 #[serde(alias = "min-width")]
1484 pub min_width: Option<f64>,
1485 #[serde(alias = "min-height")]
1487 pub min_height: Option<f64>,
1488 #[serde(alias = "max-width")]
1490 pub max_width: Option<f64>,
1491 #[serde(alias = "max-height")]
1493 pub max_height: Option<f64>,
1494 #[serde(default = "default_true")]
1496 pub resizable: bool,
1497 #[serde(default = "default_true")]
1505 pub maximizable: bool,
1506 #[serde(default = "default_true")]
1512 pub minimizable: bool,
1513 #[serde(default = "default_true")]
1521 pub closable: bool,
1522 #[serde(default = "default_title")]
1524 pub title: String,
1525 #[serde(default)]
1527 pub fullscreen: bool,
1528 #[serde(default = "default_true")]
1530 pub focus: bool,
1531 #[serde(default)]
1536 pub transparent: bool,
1537 #[serde(default)]
1539 pub maximized: bool,
1540 #[serde(default = "default_true")]
1542 pub visible: bool,
1543 #[serde(default = "default_true")]
1545 pub decorations: bool,
1546 #[serde(default, alias = "always-on-bottom")]
1548 pub always_on_bottom: bool,
1549 #[serde(default, alias = "always-on-top")]
1551 pub always_on_top: bool,
1552 #[serde(default, alias = "visible-on-all-workspaces")]
1558 pub visible_on_all_workspaces: bool,
1559 #[serde(default, alias = "content-protected")]
1561 pub content_protected: bool,
1562 #[serde(default, alias = "skip-taskbar")]
1564 pub skip_taskbar: bool,
1565 pub window_classname: Option<String>,
1567 pub theme: Option<crate::Theme>,
1569 #[serde(default, alias = "title-bar-style")]
1571 pub title_bar_style: TitleBarStyle,
1572 #[serde(default, alias = "hidden-title")]
1574 pub hidden_title: bool,
1575 #[serde(default, alias = "accept-first-mouse")]
1577 pub accept_first_mouse: bool,
1578 #[serde(default, alias = "tabbing-identifier")]
1585 pub tabbing_identifier: Option<String>,
1586 #[serde(default, alias = "additional-browser-args")]
1589 pub additional_browser_args: Option<String>,
1590 #[serde(default = "default_true")]
1600 pub shadow: bool,
1601 #[serde(default, alias = "window-effects")]
1610 pub window_effects: Option<WindowEffectsConfig>,
1611 #[serde(default)]
1617 pub incognito: bool,
1618 pub parent: Option<String>,
1630 #[serde(alias = "proxy-url")]
1638 pub proxy_url: Option<Url>,
1639 #[serde(default, alias = "zoom-hotkeys-enabled")]
1649 pub zoom_hotkeys_enabled: bool,
1650 #[serde(default, alias = "browser-extensions-enabled")]
1657 pub browser_extensions_enabled: bool,
1658
1659 #[serde(default, alias = "use-https-scheme")]
1669 pub use_https_scheme: bool,
1670 pub devtools: Option<bool>,
1680
1681 #[serde(alias = "background-color")]
1689 pub background_color: Option<Color>,
1690}
1691
1692impl Default for WindowConfig {
1693 fn default() -> Self {
1694 Self {
1695 label: default_window_label(),
1696 url: WebviewUrl::default(),
1697 create: true,
1698 user_agent: None,
1699 drag_drop_enabled: true,
1700 center: false,
1701 x: None,
1702 y: None,
1703 width: default_width(),
1704 height: default_height(),
1705 min_width: None,
1706 min_height: None,
1707 max_width: None,
1708 max_height: None,
1709 resizable: true,
1710 maximizable: true,
1711 minimizable: true,
1712 closable: true,
1713 title: default_title(),
1714 fullscreen: false,
1715 focus: false,
1716 transparent: false,
1717 maximized: false,
1718 visible: true,
1719 decorations: true,
1720 always_on_bottom: false,
1721 always_on_top: false,
1722 visible_on_all_workspaces: false,
1723 content_protected: false,
1724 skip_taskbar: false,
1725 window_classname: None,
1726 theme: None,
1727 title_bar_style: Default::default(),
1728 hidden_title: false,
1729 accept_first_mouse: false,
1730 tabbing_identifier: None,
1731 additional_browser_args: None,
1732 shadow: true,
1733 window_effects: None,
1734 incognito: false,
1735 parent: None,
1736 proxy_url: None,
1737 zoom_hotkeys_enabled: false,
1738 browser_extensions_enabled: false,
1739 use_https_scheme: false,
1740 devtools: None,
1741 background_color: None,
1742 }
1743 }
1744}
1745
1746fn default_window_label() -> String {
1747 "main".to_string()
1748}
1749
1750fn default_width() -> f64 {
1751 800f64
1752}
1753
1754fn default_height() -> f64 {
1755 600f64
1756}
1757
1758fn default_title() -> String {
1759 "Tauri App".to_string()
1760}
1761
1762#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1765#[cfg_attr(feature = "schema", derive(JsonSchema))]
1766#[serde(rename_all = "camelCase", untagged)]
1767pub enum CspDirectiveSources {
1768 Inline(String),
1770 List(Vec<String>),
1772}
1773
1774impl Default for CspDirectiveSources {
1775 fn default() -> Self {
1776 Self::List(Vec::new())
1777 }
1778}
1779
1780impl From<CspDirectiveSources> for Vec<String> {
1781 fn from(sources: CspDirectiveSources) -> Self {
1782 match sources {
1783 CspDirectiveSources::Inline(source) => source.split(' ').map(|s| s.to_string()).collect(),
1784 CspDirectiveSources::List(l) => l,
1785 }
1786 }
1787}
1788
1789impl CspDirectiveSources {
1790 pub fn contains(&self, source: &str) -> bool {
1792 match self {
1793 Self::Inline(s) => s.contains(&format!("{source} ")) || s.contains(&format!(" {source}")),
1794 Self::List(l) => l.contains(&source.into()),
1795 }
1796 }
1797
1798 pub fn push<S: AsRef<str>>(&mut self, source: S) {
1800 match self {
1801 Self::Inline(s) => {
1802 s.push(' ');
1803 s.push_str(source.as_ref());
1804 }
1805 Self::List(l) => {
1806 l.push(source.as_ref().to_string());
1807 }
1808 }
1809 }
1810
1811 pub fn extend(&mut self, sources: Vec<String>) {
1813 for s in sources {
1814 self.push(s);
1815 }
1816 }
1817}
1818
1819#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1822#[cfg_attr(feature = "schema", derive(JsonSchema))]
1823#[serde(rename_all = "camelCase", untagged)]
1824pub enum Csp {
1825 Policy(String),
1827 DirectiveMap(HashMap<String, CspDirectiveSources>),
1829}
1830
1831impl From<HashMap<String, CspDirectiveSources>> for Csp {
1832 fn from(map: HashMap<String, CspDirectiveSources>) -> Self {
1833 Self::DirectiveMap(map)
1834 }
1835}
1836
1837impl From<Csp> for HashMap<String, CspDirectiveSources> {
1838 fn from(csp: Csp) -> Self {
1839 match csp {
1840 Csp::Policy(policy) => {
1841 let mut map = HashMap::new();
1842 for directive in policy.split(';') {
1843 let mut tokens = directive.trim().split(' ');
1844 if let Some(directive) = tokens.next() {
1845 let sources = tokens.map(|s| s.to_string()).collect::<Vec<String>>();
1846 map.insert(directive.to_string(), CspDirectiveSources::List(sources));
1847 }
1848 }
1849 map
1850 }
1851 Csp::DirectiveMap(m) => m,
1852 }
1853 }
1854}
1855
1856impl Display for Csp {
1857 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1858 match self {
1859 Self::Policy(s) => write!(f, "{s}"),
1860 Self::DirectiveMap(m) => {
1861 let len = m.len();
1862 let mut i = 0;
1863 for (directive, sources) in m {
1864 let sources: Vec<String> = sources.clone().into();
1865 write!(f, "{} {}", directive, sources.join(" "))?;
1866 i += 1;
1867 if i != len {
1868 write!(f, "; ")?;
1869 }
1870 }
1871 Ok(())
1872 }
1873 }
1874 }
1875}
1876
1877#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1879#[serde(untagged)]
1880#[cfg_attr(feature = "schema", derive(JsonSchema))]
1881pub enum DisabledCspModificationKind {
1882 Flag(bool),
1885 List(Vec<String>),
1887}
1888
1889impl DisabledCspModificationKind {
1890 pub fn can_modify(&self, directive: &str) -> bool {
1892 match self {
1893 Self::Flag(f) => !f,
1894 Self::List(l) => !l.contains(&directive.into()),
1895 }
1896 }
1897}
1898
1899impl Default for DisabledCspModificationKind {
1900 fn default() -> Self {
1901 Self::Flag(false)
1902 }
1903}
1904
1905#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1914#[serde(untagged)]
1915#[cfg_attr(feature = "schema", derive(JsonSchema))]
1916pub enum FsScope {
1917 AllowedPaths(Vec<PathBuf>),
1919 #[serde(rename_all = "camelCase")]
1921 Scope {
1922 #[serde(default)]
1924 allow: Vec<PathBuf>,
1925 #[serde(default)]
1928 deny: Vec<PathBuf>,
1929 #[serde(alias = "require-literal-leading-dot")]
1938 require_literal_leading_dot: Option<bool>,
1939 },
1940}
1941
1942impl Default for FsScope {
1943 fn default() -> Self {
1944 Self::AllowedPaths(Vec::new())
1945 }
1946}
1947
1948impl FsScope {
1949 pub fn allowed_paths(&self) -> &Vec<PathBuf> {
1951 match self {
1952 Self::AllowedPaths(p) => p,
1953 Self::Scope { allow, .. } => allow,
1954 }
1955 }
1956
1957 pub fn forbidden_paths(&self) -> Option<&Vec<PathBuf>> {
1959 match self {
1960 Self::AllowedPaths(_) => None,
1961 Self::Scope { deny, .. } => Some(deny),
1962 }
1963 }
1964}
1965
1966#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1970#[cfg_attr(feature = "schema", derive(JsonSchema))]
1971#[serde(rename_all = "camelCase", deny_unknown_fields)]
1972pub struct AssetProtocolConfig {
1973 #[serde(default)]
1975 pub scope: FsScope,
1976 #[serde(default)]
1978 pub enable: bool,
1979}
1980
1981#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1985#[cfg_attr(feature = "schema", derive(JsonSchema))]
1986#[serde(rename_all = "camelCase", untagged)]
1987pub enum HeaderSource {
1988 Inline(String),
1990 List(Vec<String>),
1992 Map(HashMap<String, String>),
1994}
1995
1996impl Display for HeaderSource {
1997 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1998 match self {
1999 Self::Inline(s) => write!(f, "{s}"),
2000 Self::List(l) => write!(f, "{}", l.join(", ")),
2001 Self::Map(m) => {
2002 let len = m.len();
2003 let mut i = 0;
2004 for (key, value) in m {
2005 write!(f, "{} {}", key, value)?;
2006 i += 1;
2007 if i != len {
2008 write!(f, "; ")?;
2009 }
2010 }
2011 Ok(())
2012 }
2013 }
2014 }
2015}
2016
2017pub trait HeaderAddition {
2021 fn add_configured_headers(self, headers: Option<&HeaderConfig>) -> http::response::Builder;
2023}
2024
2025impl HeaderAddition for Builder {
2026 fn add_configured_headers(mut self, headers: Option<&HeaderConfig>) -> http::response::Builder {
2030 if let Some(headers) = headers {
2031 if let Some(value) = &headers.access_control_allow_credentials {
2033 self = self.header("Access-Control-Allow-Credentials", value.to_string());
2034 };
2035
2036 if let Some(value) = &headers.access_control_allow_headers {
2038 self = self.header("Access-Control-Allow-Headers", value.to_string());
2039 };
2040
2041 if let Some(value) = &headers.access_control_allow_methods {
2043 self = self.header("Access-Control-Allow-Methods", value.to_string());
2044 };
2045
2046 if let Some(value) = &headers.access_control_expose_headers {
2048 self = self.header("Access-Control-Expose-Headers", value.to_string());
2049 };
2050
2051 if let Some(value) = &headers.access_control_max_age {
2053 self = self.header("Access-Control-Max-Age", value.to_string());
2054 };
2055
2056 if let Some(value) = &headers.cross_origin_embedder_policy {
2058 self = self.header("Cross-Origin-Embedder-Policy", value.to_string());
2059 };
2060
2061 if let Some(value) = &headers.cross_origin_opener_policy {
2063 self = self.header("Cross-Origin-Opener-Policy", value.to_string());
2064 };
2065
2066 if let Some(value) = &headers.cross_origin_resource_policy {
2068 self = self.header("Cross-Origin-Resource-Policy", value.to_string());
2069 };
2070
2071 if let Some(value) = &headers.permissions_policy {
2073 self = self.header("Permission-Policy", value.to_string());
2074 };
2075
2076 if let Some(value) = &headers.timing_allow_origin {
2078 self = self.header("Timing-Allow-Origin", value.to_string());
2079 };
2080
2081 if let Some(value) = &headers.x_content_type_options {
2083 self = self.header("X-Content-Type-Options", value.to_string());
2084 };
2085
2086 if let Some(value) = &headers.tauri_custom_header {
2088 self = self.header("Tauri-Custom-Header", value.to_string());
2090 };
2091 }
2092 self
2093 }
2094}
2095
2096#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2147#[cfg_attr(feature = "schema", derive(JsonSchema))]
2148#[serde(deny_unknown_fields)]
2149pub struct HeaderConfig {
2150 #[serde(rename = "Access-Control-Allow-Credentials")]
2155 pub access_control_allow_credentials: Option<HeaderSource>,
2156 #[serde(rename = "Access-Control-Allow-Headers")]
2164 pub access_control_allow_headers: Option<HeaderSource>,
2165 #[serde(rename = "Access-Control-Allow-Methods")]
2170 pub access_control_allow_methods: Option<HeaderSource>,
2171 #[serde(rename = "Access-Control-Expose-Headers")]
2177 pub access_control_expose_headers: Option<HeaderSource>,
2178 #[serde(rename = "Access-Control-Max-Age")]
2185 pub access_control_max_age: Option<HeaderSource>,
2186 #[serde(rename = "Cross-Origin-Embedder-Policy")]
2191 pub cross_origin_embedder_policy: Option<HeaderSource>,
2192 #[serde(rename = "Cross-Origin-Opener-Policy")]
2199 pub cross_origin_opener_policy: Option<HeaderSource>,
2200 #[serde(rename = "Cross-Origin-Resource-Policy")]
2205 pub cross_origin_resource_policy: Option<HeaderSource>,
2206 #[serde(rename = "Permissions-Policy")]
2211 pub permissions_policy: Option<HeaderSource>,
2212 #[serde(rename = "Timing-Allow-Origin")]
2218 pub timing_allow_origin: Option<HeaderSource>,
2219 #[serde(rename = "X-Content-Type-Options")]
2226 pub x_content_type_options: Option<HeaderSource>,
2227 #[serde(rename = "Tauri-Custom-Header")]
2232 pub tauri_custom_header: Option<HeaderSource>,
2233}
2234
2235impl HeaderConfig {
2236 pub fn new() -> Self {
2238 HeaderConfig {
2239 access_control_allow_credentials: None,
2240 access_control_allow_methods: None,
2241 access_control_allow_headers: None,
2242 access_control_expose_headers: None,
2243 access_control_max_age: None,
2244 cross_origin_embedder_policy: None,
2245 cross_origin_opener_policy: None,
2246 cross_origin_resource_policy: None,
2247 permissions_policy: None,
2248 timing_allow_origin: None,
2249 x_content_type_options: None,
2250 tauri_custom_header: None,
2251 }
2252 }
2253}
2254
2255#[skip_serializing_none]
2259#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
2260#[cfg_attr(feature = "schema", derive(JsonSchema))]
2261#[serde(rename_all = "camelCase", deny_unknown_fields)]
2262pub struct SecurityConfig {
2263 pub csp: Option<Csp>,
2269 #[serde(alias = "dev-csp")]
2274 pub dev_csp: Option<Csp>,
2275 #[serde(default, alias = "freeze-prototype")]
2277 pub freeze_prototype: bool,
2278 #[serde(default, alias = "dangerous-disable-asset-csp-modification")]
2291 pub dangerous_disable_asset_csp_modification: DisabledCspModificationKind,
2292 #[serde(default, alias = "asset-protocol")]
2294 pub asset_protocol: AssetProtocolConfig,
2295 #[serde(default)]
2297 pub pattern: PatternKind,
2298 #[serde(default)]
2302 pub capabilities: Vec<CapabilityEntry>,
2303 #[serde(default)]
2306 pub headers: Option<HeaderConfig>,
2307}
2308
2309#[derive(Debug, Clone, PartialEq, Serialize)]
2311#[cfg_attr(feature = "schema", derive(JsonSchema))]
2312#[serde(untagged)]
2313pub enum CapabilityEntry {
2314 Inlined(Capability),
2316 Reference(String),
2318}
2319
2320impl<'de> Deserialize<'de> for CapabilityEntry {
2321 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2322 where
2323 D: Deserializer<'de>,
2324 {
2325 UntaggedEnumVisitor::new()
2326 .string(|string| Ok(Self::Reference(string.to_owned())))
2327 .map(|map| map.deserialize::<Capability>().map(Self::Inlined))
2328 .deserialize(deserializer)
2329 }
2330}
2331
2332#[skip_serializing_none]
2334#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
2335#[serde(rename_all = "lowercase", tag = "use", content = "options")]
2336#[cfg_attr(feature = "schema", derive(JsonSchema))]
2337pub enum PatternKind {
2338 Brownfield,
2340 Isolation {
2342 dir: PathBuf,
2344 },
2345}
2346
2347impl Default for PatternKind {
2348 fn default() -> Self {
2349 Self::Brownfield
2350 }
2351}
2352
2353#[skip_serializing_none]
2357#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
2358#[cfg_attr(feature = "schema", derive(JsonSchema))]
2359#[serde(rename_all = "camelCase", deny_unknown_fields)]
2360pub struct AppConfig {
2361 #[serde(default)]
2363 pub windows: Vec<WindowConfig>,
2364 #[serde(default)]
2366 pub security: SecurityConfig,
2367 #[serde(alias = "tray-icon")]
2369 pub tray_icon: Option<TrayIconConfig>,
2370 #[serde(rename = "macOSPrivateApi", alias = "macos-private-api", default)]
2372 pub macos_private_api: bool,
2373 #[serde(default, alias = "with-global-tauri")]
2375 pub with_global_tauri: bool,
2376 #[serde(rename = "enableGTKAppId", alias = "enable-gtk-app-id", default)]
2378 pub enable_gtk_app_id: bool,
2379}
2380
2381impl AppConfig {
2382 pub fn all_features() -> Vec<&'static str> {
2384 vec![
2385 "tray-icon",
2386 "macos-private-api",
2387 "protocol-asset",
2388 "isolation",
2389 ]
2390 }
2391
2392 pub fn features(&self) -> Vec<&str> {
2394 let mut features = Vec::new();
2395 if self.tray_icon.is_some() {
2396 features.push("tray-icon");
2397 }
2398 if self.macos_private_api {
2399 features.push("macos-private-api");
2400 }
2401 if self.security.asset_protocol.enable {
2402 features.push("protocol-asset");
2403 }
2404
2405 if let PatternKind::Isolation { .. } = self.security.pattern {
2406 features.push("isolation");
2407 }
2408
2409 features.sort_unstable();
2410 features
2411 }
2412}
2413
2414#[skip_serializing_none]
2418#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2419#[cfg_attr(feature = "schema", derive(JsonSchema))]
2420#[serde(rename_all = "camelCase", deny_unknown_fields)]
2421pub struct TrayIconConfig {
2422 pub id: Option<String>,
2424 #[serde(alias = "icon-path")]
2430 pub icon_path: PathBuf,
2431 #[serde(default, alias = "icon-as-template")]
2433 pub icon_as_template: bool,
2434 #[serde(default = "default_true", alias = "menu-on-left-click")]
2440 #[deprecated(since = "2.2.0", note = "Use `show_menu_on_left_click` instead.")]
2441 pub menu_on_left_click: bool,
2442 #[serde(default = "default_true", alias = "show-menu-on-left-click")]
2448 pub show_menu_on_left_click: bool,
2449 pub title: Option<String>,
2451 pub tooltip: Option<String>,
2453}
2454
2455#[skip_serializing_none]
2457#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2458#[cfg_attr(feature = "schema", derive(JsonSchema))]
2459#[serde(rename_all = "camelCase", deny_unknown_fields)]
2460pub struct IosConfig {
2461 pub template: Option<PathBuf>,
2465 pub frameworks: Option<Vec<String>>,
2469 #[serde(alias = "development-team")]
2472 pub development_team: Option<String>,
2473 #[serde(
2477 alias = "minimum-system-version",
2478 default = "ios_minimum_system_version"
2479 )]
2480 pub minimum_system_version: String,
2481}
2482
2483impl Default for IosConfig {
2484 fn default() -> Self {
2485 Self {
2486 template: None,
2487 frameworks: None,
2488 development_team: None,
2489 minimum_system_version: ios_minimum_system_version(),
2490 }
2491 }
2492}
2493
2494#[skip_serializing_none]
2496#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2497#[cfg_attr(feature = "schema", derive(JsonSchema))]
2498#[serde(rename_all = "camelCase", deny_unknown_fields)]
2499pub struct AndroidConfig {
2500 #[serde(alias = "min-sdk-version", default = "default_min_sdk_version")]
2503 pub min_sdk_version: u32,
2504
2505 #[serde(alias = "version-code")]
2511 #[cfg_attr(feature = "schema", validate(range(min = 1, max = 2_100_000_000)))]
2512 pub version_code: Option<u32>,
2513}
2514
2515impl Default for AndroidConfig {
2516 fn default() -> Self {
2517 Self {
2518 min_sdk_version: default_min_sdk_version(),
2519 version_code: None,
2520 }
2521 }
2522}
2523
2524fn default_min_sdk_version() -> u32 {
2525 24
2526}
2527
2528#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2530#[cfg_attr(feature = "schema", derive(JsonSchema))]
2531#[serde(untagged, deny_unknown_fields)]
2532#[non_exhaustive]
2533pub enum FrontendDist {
2534 Url(Url),
2536 Directory(PathBuf),
2538 Files(Vec<PathBuf>),
2540}
2541
2542impl std::fmt::Display for FrontendDist {
2543 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2544 match self {
2545 Self::Url(url) => write!(f, "{url}"),
2546 Self::Directory(p) => write!(f, "{}", p.display()),
2547 Self::Files(files) => write!(f, "{}", serde_json::to_string(files).unwrap()),
2548 }
2549 }
2550}
2551
2552#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2554#[cfg_attr(feature = "schema", derive(JsonSchema))]
2555#[serde(rename_all = "camelCase", untagged)]
2556pub enum BeforeDevCommand {
2557 Script(String),
2559 ScriptWithOptions {
2561 script: String,
2563 cwd: Option<String>,
2565 #[serde(default)]
2567 wait: bool,
2568 },
2569}
2570
2571#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2573#[cfg_attr(feature = "schema", derive(JsonSchema))]
2574#[serde(rename_all = "camelCase", untagged)]
2575pub enum HookCommand {
2576 Script(String),
2578 ScriptWithOptions {
2580 script: String,
2582 cwd: Option<String>,
2584 },
2585}
2586
2587#[skip_serializing_none]
2591#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Default)]
2592#[cfg_attr(feature = "schema", derive(JsonSchema))]
2593#[serde(rename_all = "camelCase", deny_unknown_fields)]
2594pub struct BuildConfig {
2595 pub runner: Option<String>,
2597 #[serde(alias = "dev-url")]
2605 pub dev_url: Option<Url>,
2606 #[serde(alias = "frontend-dist")]
2620 pub frontend_dist: Option<FrontendDist>,
2621 #[serde(alias = "before-dev-command")]
2625 pub before_dev_command: Option<BeforeDevCommand>,
2626 #[serde(alias = "before-build-command")]
2630 pub before_build_command: Option<HookCommand>,
2631 #[serde(alias = "before-bundle-command")]
2635 pub before_bundle_command: Option<HookCommand>,
2636 pub features: Option<Vec<String>>,
2638}
2639
2640#[derive(Debug, PartialEq, Eq)]
2641struct PackageVersion(String);
2642
2643impl<'d> serde::Deserialize<'d> for PackageVersion {
2644 fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {
2645 struct PackageVersionVisitor;
2646
2647 impl Visitor<'_> for PackageVersionVisitor {
2648 type Value = PackageVersion;
2649
2650 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2651 write!(
2652 formatter,
2653 "a semver string or a path to a package.json file"
2654 )
2655 }
2656
2657 fn visit_str<E: DeError>(self, value: &str) -> Result<PackageVersion, E> {
2658 let path = PathBuf::from(value);
2659 if path.exists() {
2660 let json_str = read_to_string(&path)
2661 .map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?;
2662 let package_json: serde_json::Value = serde_json::from_str(&json_str)
2663 .map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?;
2664 if let Some(obj) = package_json.as_object() {
2665 let version = obj
2666 .get("version")
2667 .ok_or_else(|| DeError::custom("JSON must contain a `version` field"))?
2668 .as_str()
2669 .ok_or_else(|| {
2670 DeError::custom(format!("`{} > version` must be a string", path.display()))
2671 })?;
2672 Ok(PackageVersion(
2673 Version::from_str(version)
2674 .map_err(|_| DeError::custom("`package > version` must be a semver string"))?
2675 .to_string(),
2676 ))
2677 } else {
2678 Err(DeError::custom(
2679 "`package > version` value is not a path to a JSON object",
2680 ))
2681 }
2682 } else {
2683 Ok(PackageVersion(
2684 Version::from_str(value)
2685 .map_err(|_| DeError::custom("`package > version` must be a semver string"))?
2686 .to_string(),
2687 ))
2688 }
2689 }
2690 }
2691
2692 deserializer.deserialize_string(PackageVersionVisitor {})
2693 }
2694}
2695
2696fn version_deserializer<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
2697where
2698 D: Deserializer<'de>,
2699{
2700 Option::<PackageVersion>::deserialize(deserializer).map(|v| v.map(|v| v.0))
2701}
2702
2703#[skip_serializing_none]
2769#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
2770#[cfg_attr(feature = "schema", derive(JsonSchema))]
2771#[serde(rename_all = "camelCase", deny_unknown_fields)]
2772pub struct Config {
2773 #[serde(rename = "$schema")]
2775 pub schema: Option<String>,
2776 #[serde(alias = "product-name")]
2778 #[cfg_attr(feature = "schema", validate(regex(pattern = "^[^/\\:*?\"<>|]+$")))]
2779 pub product_name: Option<String>,
2780 #[serde(alias = "main-binary-name")]
2782 pub main_binary_name: Option<String>,
2783 #[serde(deserialize_with = "version_deserializer", default)]
2787 pub version: Option<String>,
2788 pub identifier: String,
2794 #[serde(default)]
2796 pub app: AppConfig,
2797 #[serde(default = "default_build")]
2799 pub build: BuildConfig,
2800 #[serde(default)]
2802 pub bundle: BundleConfig,
2803 #[serde(default)]
2805 pub plugins: PluginConfig,
2806}
2807
2808#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
2812#[cfg_attr(feature = "schema", derive(JsonSchema))]
2813pub struct PluginConfig(pub HashMap<String, JsonValue>);
2814
2815fn default_build() -> BuildConfig {
2816 BuildConfig {
2817 runner: None,
2818 dev_url: None,
2819 frontend_dist: None,
2820 before_dev_command: None,
2821 before_build_command: None,
2822 before_bundle_command: None,
2823 features: None,
2824 }
2825}
2826
2827#[cfg(feature = "build")]
2833mod build {
2834 use super::*;
2835 use crate::{literal_struct, tokens::*};
2836 use proc_macro2::TokenStream;
2837 use quote::{quote, ToTokens, TokenStreamExt};
2838 use std::convert::identity;
2839
2840 impl ToTokens for WebviewUrl {
2841 fn to_tokens(&self, tokens: &mut TokenStream) {
2842 let prefix = quote! { ::tauri::utils::config::WebviewUrl };
2843
2844 tokens.append_all(match self {
2845 Self::App(path) => {
2846 let path = path_buf_lit(path);
2847 quote! { #prefix::App(#path) }
2848 }
2849 Self::External(url) => {
2850 let url = url_lit(url);
2851 quote! { #prefix::External(#url) }
2852 }
2853 Self::CustomProtocol(url) => {
2854 let url = url_lit(url);
2855 quote! { #prefix::CustomProtocol(#url) }
2856 }
2857 })
2858 }
2859 }
2860
2861 impl ToTokens for crate::Theme {
2862 fn to_tokens(&self, tokens: &mut TokenStream) {
2863 let prefix = quote! { ::tauri::utils::Theme };
2864
2865 tokens.append_all(match self {
2866 Self::Light => quote! { #prefix::Light },
2867 Self::Dark => quote! { #prefix::Dark },
2868 })
2869 }
2870 }
2871
2872 impl ToTokens for Color {
2873 fn to_tokens(&self, tokens: &mut TokenStream) {
2874 let Color(r, g, b, a) = self;
2875 tokens.append_all(quote! {::tauri::utils::config::Color(#r,#g,#b,#a)});
2876 }
2877 }
2878 impl ToTokens for WindowEffectsConfig {
2879 fn to_tokens(&self, tokens: &mut TokenStream) {
2880 let effects = vec_lit(self.effects.clone(), |d| d);
2881 let state = opt_lit(self.state.as_ref());
2882 let radius = opt_lit(self.radius.as_ref());
2883 let color = opt_lit(self.color.as_ref());
2884
2885 literal_struct!(
2886 tokens,
2887 ::tauri::utils::config::WindowEffectsConfig,
2888 effects,
2889 state,
2890 radius,
2891 color
2892 )
2893 }
2894 }
2895
2896 impl ToTokens for crate::TitleBarStyle {
2897 fn to_tokens(&self, tokens: &mut TokenStream) {
2898 let prefix = quote! { ::tauri::utils::TitleBarStyle };
2899
2900 tokens.append_all(match self {
2901 Self::Visible => quote! { #prefix::Visible },
2902 Self::Transparent => quote! { #prefix::Transparent },
2903 Self::Overlay => quote! { #prefix::Overlay },
2904 })
2905 }
2906 }
2907
2908 impl ToTokens for crate::WindowEffect {
2909 fn to_tokens(&self, tokens: &mut TokenStream) {
2910 let prefix = quote! { ::tauri::utils::WindowEffect };
2911
2912 #[allow(deprecated)]
2913 tokens.append_all(match self {
2914 WindowEffect::AppearanceBased => quote! { #prefix::AppearanceBased},
2915 WindowEffect::Light => quote! { #prefix::Light},
2916 WindowEffect::Dark => quote! { #prefix::Dark},
2917 WindowEffect::MediumLight => quote! { #prefix::MediumLight},
2918 WindowEffect::UltraDark => quote! { #prefix::UltraDark},
2919 WindowEffect::Titlebar => quote! { #prefix::Titlebar},
2920 WindowEffect::Selection => quote! { #prefix::Selection},
2921 WindowEffect::Menu => quote! { #prefix::Menu},
2922 WindowEffect::Popover => quote! { #prefix::Popover},
2923 WindowEffect::Sidebar => quote! { #prefix::Sidebar},
2924 WindowEffect::HeaderView => quote! { #prefix::HeaderView},
2925 WindowEffect::Sheet => quote! { #prefix::Sheet},
2926 WindowEffect::WindowBackground => quote! { #prefix::WindowBackground},
2927 WindowEffect::HudWindow => quote! { #prefix::HudWindow},
2928 WindowEffect::FullScreenUI => quote! { #prefix::FullScreenUI},
2929 WindowEffect::Tooltip => quote! { #prefix::Tooltip},
2930 WindowEffect::ContentBackground => quote! { #prefix::ContentBackground},
2931 WindowEffect::UnderWindowBackground => quote! { #prefix::UnderWindowBackground},
2932 WindowEffect::UnderPageBackground => quote! { #prefix::UnderPageBackground},
2933 WindowEffect::Mica => quote! { #prefix::Mica},
2934 WindowEffect::MicaDark => quote! { #prefix::MicaDark},
2935 WindowEffect::MicaLight => quote! { #prefix::MicaLight},
2936 WindowEffect::Blur => quote! { #prefix::Blur},
2937 WindowEffect::Acrylic => quote! { #prefix::Acrylic},
2938 WindowEffect::Tabbed => quote! { #prefix::Tabbed },
2939 WindowEffect::TabbedDark => quote! { #prefix::TabbedDark },
2940 WindowEffect::TabbedLight => quote! { #prefix::TabbedLight },
2941 })
2942 }
2943 }
2944
2945 impl ToTokens for crate::WindowEffectState {
2946 fn to_tokens(&self, tokens: &mut TokenStream) {
2947 let prefix = quote! { ::tauri::utils::WindowEffectState };
2948
2949 #[allow(deprecated)]
2950 tokens.append_all(match self {
2951 WindowEffectState::Active => quote! { #prefix::Active},
2952 WindowEffectState::FollowsWindowActiveState => quote! { #prefix::FollowsWindowActiveState},
2953 WindowEffectState::Inactive => quote! { #prefix::Inactive},
2954 })
2955 }
2956 }
2957
2958 impl ToTokens for WindowConfig {
2959 fn to_tokens(&self, tokens: &mut TokenStream) {
2960 let label = str_lit(&self.label);
2961 let create = &self.create;
2962 let url = &self.url;
2963 let user_agent = opt_str_lit(self.user_agent.as_ref());
2964 let drag_drop_enabled = self.drag_drop_enabled;
2965 let center = self.center;
2966 let x = opt_lit(self.x.as_ref());
2967 let y = opt_lit(self.y.as_ref());
2968 let width = self.width;
2969 let height = self.height;
2970 let min_width = opt_lit(self.min_width.as_ref());
2971 let min_height = opt_lit(self.min_height.as_ref());
2972 let max_width = opt_lit(self.max_width.as_ref());
2973 let max_height = opt_lit(self.max_height.as_ref());
2974 let resizable = self.resizable;
2975 let maximizable = self.maximizable;
2976 let minimizable = self.minimizable;
2977 let closable = self.closable;
2978 let title = str_lit(&self.title);
2979 let proxy_url = opt_lit(self.proxy_url.as_ref().map(url_lit).as_ref());
2980 let fullscreen = self.fullscreen;
2981 let focus = self.focus;
2982 let transparent = self.transparent;
2983 let maximized = self.maximized;
2984 let visible = self.visible;
2985 let decorations = self.decorations;
2986 let always_on_bottom = self.always_on_bottom;
2987 let always_on_top = self.always_on_top;
2988 let visible_on_all_workspaces = self.visible_on_all_workspaces;
2989 let content_protected = self.content_protected;
2990 let skip_taskbar = self.skip_taskbar;
2991 let window_classname = opt_str_lit(self.window_classname.as_ref());
2992 let theme = opt_lit(self.theme.as_ref());
2993 let title_bar_style = &self.title_bar_style;
2994 let hidden_title = self.hidden_title;
2995 let accept_first_mouse = self.accept_first_mouse;
2996 let tabbing_identifier = opt_str_lit(self.tabbing_identifier.as_ref());
2997 let additional_browser_args = opt_str_lit(self.additional_browser_args.as_ref());
2998 let shadow = self.shadow;
2999 let window_effects = opt_lit(self.window_effects.as_ref());
3000 let incognito = self.incognito;
3001 let parent = opt_str_lit(self.parent.as_ref());
3002 let zoom_hotkeys_enabled = self.zoom_hotkeys_enabled;
3003 let browser_extensions_enabled = self.browser_extensions_enabled;
3004 let use_https_scheme = self.use_https_scheme;
3005 let devtools = opt_lit(self.devtools.as_ref());
3006 let background_color = opt_lit(self.background_color.as_ref());
3007
3008 literal_struct!(
3009 tokens,
3010 ::tauri::utils::config::WindowConfig,
3011 label,
3012 url,
3013 create,
3014 user_agent,
3015 drag_drop_enabled,
3016 center,
3017 x,
3018 y,
3019 width,
3020 height,
3021 min_width,
3022 min_height,
3023 max_width,
3024 max_height,
3025 resizable,
3026 maximizable,
3027 minimizable,
3028 closable,
3029 title,
3030 proxy_url,
3031 fullscreen,
3032 focus,
3033 transparent,
3034 maximized,
3035 visible,
3036 decorations,
3037 always_on_bottom,
3038 always_on_top,
3039 visible_on_all_workspaces,
3040 content_protected,
3041 skip_taskbar,
3042 window_classname,
3043 theme,
3044 title_bar_style,
3045 hidden_title,
3046 accept_first_mouse,
3047 tabbing_identifier,
3048 additional_browser_args,
3049 shadow,
3050 window_effects,
3051 incognito,
3052 parent,
3053 zoom_hotkeys_enabled,
3054 browser_extensions_enabled,
3055 use_https_scheme,
3056 devtools,
3057 background_color
3058 );
3059 }
3060 }
3061
3062 impl ToTokens for PatternKind {
3063 fn to_tokens(&self, tokens: &mut TokenStream) {
3064 let prefix = quote! { ::tauri::utils::config::PatternKind };
3065
3066 tokens.append_all(match self {
3067 Self::Brownfield => quote! { #prefix::Brownfield },
3068 #[cfg(not(feature = "isolation"))]
3069 Self::Isolation { dir: _ } => quote! { #prefix::Brownfield },
3070 #[cfg(feature = "isolation")]
3071 Self::Isolation { dir } => {
3072 let dir = path_buf_lit(dir);
3073 quote! { #prefix::Isolation { dir: #dir } }
3074 }
3075 })
3076 }
3077 }
3078
3079 impl ToTokens for WebviewInstallMode {
3080 fn to_tokens(&self, tokens: &mut TokenStream) {
3081 let prefix = quote! { ::tauri::utils::config::WebviewInstallMode };
3082
3083 tokens.append_all(match self {
3084 Self::Skip => quote! { #prefix::Skip },
3085 Self::DownloadBootstrapper { silent } => {
3086 quote! { #prefix::DownloadBootstrapper { silent: #silent } }
3087 }
3088 Self::EmbedBootstrapper { silent } => {
3089 quote! { #prefix::EmbedBootstrapper { silent: #silent } }
3090 }
3091 Self::OfflineInstaller { silent } => {
3092 quote! { #prefix::OfflineInstaller { silent: #silent } }
3093 }
3094 Self::FixedRuntime { path } => {
3095 let path = path_buf_lit(path);
3096 quote! { #prefix::FixedRuntime { path: #path } }
3097 }
3098 })
3099 }
3100 }
3101
3102 impl ToTokens for WindowsConfig {
3103 fn to_tokens(&self, tokens: &mut TokenStream) {
3104 let webview_install_mode = &self.webview_install_mode;
3105 tokens.append_all(quote! { ::tauri::utils::config::WindowsConfig {
3106 webview_install_mode: #webview_install_mode,
3107 ..Default::default()
3108 }})
3109 }
3110 }
3111
3112 impl ToTokens for BundleConfig {
3113 fn to_tokens(&self, tokens: &mut TokenStream) {
3114 let publisher = quote!(None);
3115 let homepage = quote!(None);
3116 let icon = vec_lit(&self.icon, str_lit);
3117 let active = self.active;
3118 let targets = quote!(Default::default());
3119 let create_updater_artifacts = quote!(Default::default());
3120 let resources = quote!(None);
3121 let copyright = quote!(None);
3122 let category = quote!(None);
3123 let file_associations = quote!(None);
3124 let short_description = quote!(None);
3125 let long_description = quote!(None);
3126 let use_local_tools_dir = self.use_local_tools_dir;
3127 let external_bin = opt_vec_lit(self.external_bin.as_ref(), str_lit);
3128 let windows = &self.windows;
3129 let license = opt_str_lit(self.license.as_ref());
3130 let license_file = opt_lit(self.license_file.as_ref().map(path_buf_lit).as_ref());
3131 let linux = quote!(Default::default());
3132 let macos = quote!(Default::default());
3133 let ios = quote!(Default::default());
3134 let android = quote!(Default::default());
3135
3136 literal_struct!(
3137 tokens,
3138 ::tauri::utils::config::BundleConfig,
3139 active,
3140 publisher,
3141 homepage,
3142 icon,
3143 targets,
3144 create_updater_artifacts,
3145 resources,
3146 copyright,
3147 category,
3148 license,
3149 license_file,
3150 file_associations,
3151 short_description,
3152 long_description,
3153 use_local_tools_dir,
3154 external_bin,
3155 windows,
3156 linux,
3157 macos,
3158 ios,
3159 android
3160 );
3161 }
3162 }
3163
3164 impl ToTokens for FrontendDist {
3165 fn to_tokens(&self, tokens: &mut TokenStream) {
3166 let prefix = quote! { ::tauri::utils::config::FrontendDist };
3167
3168 tokens.append_all(match self {
3169 Self::Url(url) => {
3170 let url = url_lit(url);
3171 quote! { #prefix::Url(#url) }
3172 }
3173 Self::Directory(path) => {
3174 let path = path_buf_lit(path);
3175 quote! { #prefix::Directory(#path) }
3176 }
3177 Self::Files(files) => {
3178 let files = vec_lit(files, path_buf_lit);
3179 quote! { #prefix::Files(#files) }
3180 }
3181 })
3182 }
3183 }
3184
3185 impl ToTokens for BuildConfig {
3186 fn to_tokens(&self, tokens: &mut TokenStream) {
3187 let dev_url = opt_lit(self.dev_url.as_ref().map(url_lit).as_ref());
3188 let frontend_dist = opt_lit(self.frontend_dist.as_ref());
3189 let runner = quote!(None);
3190 let before_dev_command = quote!(None);
3191 let before_build_command = quote!(None);
3192 let before_bundle_command = quote!(None);
3193 let features = quote!(None);
3194
3195 literal_struct!(
3196 tokens,
3197 ::tauri::utils::config::BuildConfig,
3198 runner,
3199 dev_url,
3200 frontend_dist,
3201 before_dev_command,
3202 before_build_command,
3203 before_bundle_command,
3204 features
3205 );
3206 }
3207 }
3208
3209 impl ToTokens for CspDirectiveSources {
3210 fn to_tokens(&self, tokens: &mut TokenStream) {
3211 let prefix = quote! { ::tauri::utils::config::CspDirectiveSources };
3212
3213 tokens.append_all(match self {
3214 Self::Inline(sources) => {
3215 let sources = sources.as_str();
3216 quote!(#prefix::Inline(#sources.into()))
3217 }
3218 Self::List(list) => {
3219 let list = vec_lit(list, str_lit);
3220 quote!(#prefix::List(#list))
3221 }
3222 })
3223 }
3224 }
3225
3226 impl ToTokens for Csp {
3227 fn to_tokens(&self, tokens: &mut TokenStream) {
3228 let prefix = quote! { ::tauri::utils::config::Csp };
3229
3230 tokens.append_all(match self {
3231 Self::Policy(policy) => {
3232 let policy = policy.as_str();
3233 quote!(#prefix::Policy(#policy.into()))
3234 }
3235 Self::DirectiveMap(list) => {
3236 let map = map_lit(
3237 quote! { ::std::collections::HashMap },
3238 list,
3239 str_lit,
3240 identity,
3241 );
3242 quote!(#prefix::DirectiveMap(#map))
3243 }
3244 })
3245 }
3246 }
3247
3248 impl ToTokens for DisabledCspModificationKind {
3249 fn to_tokens(&self, tokens: &mut TokenStream) {
3250 let prefix = quote! { ::tauri::utils::config::DisabledCspModificationKind };
3251
3252 tokens.append_all(match self {
3253 Self::Flag(flag) => {
3254 quote! { #prefix::Flag(#flag) }
3255 }
3256 Self::List(directives) => {
3257 let directives = vec_lit(directives, str_lit);
3258 quote! { #prefix::List(#directives) }
3259 }
3260 });
3261 }
3262 }
3263
3264 impl ToTokens for CapabilityEntry {
3265 fn to_tokens(&self, tokens: &mut TokenStream) {
3266 let prefix = quote! { ::tauri::utils::config::CapabilityEntry };
3267
3268 tokens.append_all(match self {
3269 Self::Inlined(capability) => {
3270 quote! { #prefix::Inlined(#capability) }
3271 }
3272 Self::Reference(id) => {
3273 let id = str_lit(id);
3274 quote! { #prefix::Reference(#id) }
3275 }
3276 });
3277 }
3278 }
3279
3280 impl ToTokens for HeaderSource {
3281 fn to_tokens(&self, tokens: &mut TokenStream) {
3282 let prefix = quote! { ::tauri::utils::config::HeaderSource };
3283
3284 tokens.append_all(match self {
3285 Self::Inline(s) => {
3286 let line = s.as_str();
3287 quote!(#prefix::Inline(#line.into()))
3288 }
3289 Self::List(l) => {
3290 let list = vec_lit(l, str_lit);
3291 quote!(#prefix::List(#list))
3292 }
3293 Self::Map(m) => {
3294 let map = map_lit(quote! { ::std::collections::HashMap }, m, str_lit, str_lit);
3295 quote!(#prefix::Map(#map))
3296 }
3297 })
3298 }
3299 }
3300
3301 impl ToTokens for HeaderConfig {
3302 fn to_tokens(&self, tokens: &mut TokenStream) {
3303 let access_control_allow_credentials =
3304 opt_lit(self.access_control_allow_credentials.as_ref());
3305 let access_control_allow_headers = opt_lit(self.access_control_allow_headers.as_ref());
3306 let access_control_allow_methods = opt_lit(self.access_control_allow_methods.as_ref());
3307 let access_control_expose_headers = opt_lit(self.access_control_expose_headers.as_ref());
3308 let access_control_max_age = opt_lit(self.access_control_max_age.as_ref());
3309 let cross_origin_embedder_policy = opt_lit(self.cross_origin_embedder_policy.as_ref());
3310 let cross_origin_opener_policy = opt_lit(self.cross_origin_opener_policy.as_ref());
3311 let cross_origin_resource_policy = opt_lit(self.cross_origin_resource_policy.as_ref());
3312 let permissions_policy = opt_lit(self.permissions_policy.as_ref());
3313 let timing_allow_origin = opt_lit(self.timing_allow_origin.as_ref());
3314 let x_content_type_options = opt_lit(self.x_content_type_options.as_ref());
3315 let tauri_custom_header = opt_lit(self.tauri_custom_header.as_ref());
3316
3317 literal_struct!(
3318 tokens,
3319 ::tauri::utils::config::HeaderConfig,
3320 access_control_allow_credentials,
3321 access_control_allow_headers,
3322 access_control_allow_methods,
3323 access_control_expose_headers,
3324 access_control_max_age,
3325 cross_origin_embedder_policy,
3326 cross_origin_opener_policy,
3327 cross_origin_resource_policy,
3328 permissions_policy,
3329 timing_allow_origin,
3330 x_content_type_options,
3331 tauri_custom_header
3332 );
3333 }
3334 }
3335
3336 impl ToTokens for SecurityConfig {
3337 fn to_tokens(&self, tokens: &mut TokenStream) {
3338 let csp = opt_lit(self.csp.as_ref());
3339 let dev_csp = opt_lit(self.dev_csp.as_ref());
3340 let freeze_prototype = self.freeze_prototype;
3341 let dangerous_disable_asset_csp_modification = &self.dangerous_disable_asset_csp_modification;
3342 let asset_protocol = &self.asset_protocol;
3343 let pattern = &self.pattern;
3344 let capabilities = vec_lit(&self.capabilities, identity);
3345 let headers = opt_lit(self.headers.as_ref());
3346
3347 literal_struct!(
3348 tokens,
3349 ::tauri::utils::config::SecurityConfig,
3350 csp,
3351 dev_csp,
3352 freeze_prototype,
3353 dangerous_disable_asset_csp_modification,
3354 asset_protocol,
3355 pattern,
3356 capabilities,
3357 headers
3358 );
3359 }
3360 }
3361
3362 impl ToTokens for TrayIconConfig {
3363 fn to_tokens(&self, tokens: &mut TokenStream) {
3364 let id = opt_str_lit(self.id.as_ref());
3365 let icon_as_template = self.icon_as_template;
3366 #[allow(deprecated)]
3367 let menu_on_left_click = self.menu_on_left_click;
3368 let show_menu_on_left_click = self.show_menu_on_left_click;
3369 let icon_path = path_buf_lit(&self.icon_path);
3370 let title = opt_str_lit(self.title.as_ref());
3371 let tooltip = opt_str_lit(self.tooltip.as_ref());
3372 literal_struct!(
3373 tokens,
3374 ::tauri::utils::config::TrayIconConfig,
3375 id,
3376 icon_path,
3377 icon_as_template,
3378 menu_on_left_click,
3379 show_menu_on_left_click,
3380 title,
3381 tooltip
3382 );
3383 }
3384 }
3385
3386 impl ToTokens for FsScope {
3387 fn to_tokens(&self, tokens: &mut TokenStream) {
3388 let prefix = quote! { ::tauri::utils::config::FsScope };
3389
3390 tokens.append_all(match self {
3391 Self::AllowedPaths(allow) => {
3392 let allowed_paths = vec_lit(allow, path_buf_lit);
3393 quote! { #prefix::AllowedPaths(#allowed_paths) }
3394 }
3395 Self::Scope { allow, deny , require_literal_leading_dot} => {
3396 let allow = vec_lit(allow, path_buf_lit);
3397 let deny = vec_lit(deny, path_buf_lit);
3398 let require_literal_leading_dot = opt_lit(require_literal_leading_dot.as_ref());
3399 quote! { #prefix::Scope { allow: #allow, deny: #deny, require_literal_leading_dot: #require_literal_leading_dot } }
3400 }
3401 });
3402 }
3403 }
3404
3405 impl ToTokens for AssetProtocolConfig {
3406 fn to_tokens(&self, tokens: &mut TokenStream) {
3407 let scope = &self.scope;
3408 tokens.append_all(quote! { ::tauri::utils::config::AssetProtocolConfig { scope: #scope, ..Default::default() } })
3409 }
3410 }
3411
3412 impl ToTokens for AppConfig {
3413 fn to_tokens(&self, tokens: &mut TokenStream) {
3414 let windows = vec_lit(&self.windows, identity);
3415 let security = &self.security;
3416 let tray_icon = opt_lit(self.tray_icon.as_ref());
3417 let macos_private_api = self.macos_private_api;
3418 let with_global_tauri = self.with_global_tauri;
3419 let enable_gtk_app_id = self.enable_gtk_app_id;
3420
3421 literal_struct!(
3422 tokens,
3423 ::tauri::utils::config::AppConfig,
3424 windows,
3425 security,
3426 tray_icon,
3427 macos_private_api,
3428 with_global_tauri,
3429 enable_gtk_app_id
3430 );
3431 }
3432 }
3433
3434 impl ToTokens for PluginConfig {
3435 fn to_tokens(&self, tokens: &mut TokenStream) {
3436 let config = map_lit(
3437 quote! { ::std::collections::HashMap },
3438 &self.0,
3439 str_lit,
3440 json_value_lit,
3441 );
3442 tokens.append_all(quote! { ::tauri::utils::config::PluginConfig(#config) })
3443 }
3444 }
3445
3446 impl ToTokens for Config {
3447 fn to_tokens(&self, tokens: &mut TokenStream) {
3448 let schema = quote!(None);
3449 let product_name = opt_str_lit(self.product_name.as_ref());
3450 let main_binary_name = opt_str_lit(self.main_binary_name.as_ref());
3451 let version = opt_str_lit(self.version.as_ref());
3452 let identifier = str_lit(&self.identifier);
3453 let app = &self.app;
3454 let build = &self.build;
3455 let bundle = &self.bundle;
3456 let plugins = &self.plugins;
3457
3458 literal_struct!(
3459 tokens,
3460 ::tauri::utils::config::Config,
3461 schema,
3462 product_name,
3463 main_binary_name,
3464 version,
3465 identifier,
3466 app,
3467 build,
3468 bundle,
3469 plugins
3470 );
3471 }
3472 }
3473}
3474
3475#[cfg(test)]
3476mod test {
3477 use super::*;
3478
3479 #[test]
3482 fn test_defaults() {
3484 let a_config = AppConfig::default();
3486 let b_config = BuildConfig::default();
3488 let d_windows: Vec<WindowConfig> = vec![];
3490 let d_bundle = BundleConfig::default();
3492
3493 let app = AppConfig {
3495 windows: vec![],
3496 security: SecurityConfig {
3497 csp: None,
3498 dev_csp: None,
3499 freeze_prototype: false,
3500 dangerous_disable_asset_csp_modification: DisabledCspModificationKind::Flag(false),
3501 asset_protocol: AssetProtocolConfig::default(),
3502 pattern: Default::default(),
3503 capabilities: Vec::new(),
3504 headers: None,
3505 },
3506 tray_icon: None,
3507 macos_private_api: false,
3508 with_global_tauri: false,
3509 enable_gtk_app_id: false,
3510 };
3511
3512 let build = BuildConfig {
3514 runner: None,
3515 dev_url: None,
3516 frontend_dist: None,
3517 before_dev_command: None,
3518 before_build_command: None,
3519 before_bundle_command: None,
3520 features: None,
3521 };
3522
3523 let bundle = BundleConfig {
3525 active: false,
3526 targets: Default::default(),
3527 create_updater_artifacts: Default::default(),
3528 publisher: None,
3529 homepage: None,
3530 icon: Vec::new(),
3531 resources: None,
3532 copyright: None,
3533 category: None,
3534 file_associations: None,
3535 short_description: None,
3536 long_description: None,
3537 use_local_tools_dir: false,
3538 license: None,
3539 license_file: None,
3540 linux: Default::default(),
3541 macos: Default::default(),
3542 external_bin: None,
3543 windows: Default::default(),
3544 ios: Default::default(),
3545 android: Default::default(),
3546 };
3547
3548 assert_eq!(a_config, app);
3550 assert_eq!(b_config, build);
3551 assert_eq!(d_bundle, bundle);
3552 assert_eq!(d_windows, app.windows);
3553 }
3554
3555 #[test]
3556 fn parse_hex_color() {
3557 use super::Color;
3558
3559 assert_eq!(Color(255, 255, 255, 255), "fff".parse().unwrap());
3560 assert_eq!(Color(255, 255, 255, 255), "#fff".parse().unwrap());
3561 assert_eq!(Color(0, 0, 0, 255), "#000000".parse().unwrap());
3562 assert_eq!(Color(0, 0, 0, 255), "#000000ff".parse().unwrap());
3563 assert_eq!(Color(0, 255, 0, 255), "#00ff00ff".parse().unwrap());
3564 }
3565}