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, Clone, Deserialize, Serialize)]
516#[cfg_attr(feature = "schema", derive(JsonSchema))]
517#[serde(rename_all = "camelCase", deny_unknown_fields)]
518pub struct LogicalPosition {
519 pub x: f64,
521 pub y: f64,
523}
524
525#[derive(Default, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
527#[cfg_attr(feature = "schema", derive(JsonSchema))]
528#[serde(rename_all = "camelCase", deny_unknown_fields)]
529pub struct Size {
530 pub width: u32,
532 pub height: u32,
534}
535
536#[skip_serializing_none]
540#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
541#[cfg_attr(feature = "schema", derive(JsonSchema))]
542#[serde(rename_all = "camelCase", deny_unknown_fields)]
543pub struct DmgConfig {
544 pub background: Option<PathBuf>,
546 pub window_position: Option<Position>,
548 #[serde(default = "dmg_window_size", alias = "window-size")]
550 pub window_size: Size,
551 #[serde(default = "dmg_app_position", alias = "app-position")]
553 pub app_position: Position,
554 #[serde(
556 default = "dmg_application_folder_position",
557 alias = "application-folder-position"
558 )]
559 pub application_folder_position: Position,
560}
561
562impl Default for DmgConfig {
563 fn default() -> Self {
564 Self {
565 background: None,
566 window_position: None,
567 window_size: dmg_window_size(),
568 app_position: dmg_app_position(),
569 application_folder_position: dmg_application_folder_position(),
570 }
571 }
572}
573
574fn dmg_window_size() -> Size {
575 Size {
576 width: 660,
577 height: 400,
578 }
579}
580
581fn dmg_app_position() -> Position {
582 Position { x: 180, y: 170 }
583}
584
585fn dmg_application_folder_position() -> Position {
586 Position { x: 480, y: 170 }
587}
588
589fn de_macos_minimum_system_version<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
590where
591 D: Deserializer<'de>,
592{
593 let version = Option::<String>::deserialize(deserializer)?;
594 match version {
595 Some(v) if v.is_empty() => Ok(macos_minimum_system_version()),
596 e => Ok(e),
597 }
598}
599
600#[skip_serializing_none]
604#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
605#[cfg_attr(feature = "schema", derive(JsonSchema))]
606#[serde(rename_all = "camelCase", deny_unknown_fields)]
607pub struct MacConfig {
608 pub frameworks: Option<Vec<String>>,
612 #[serde(default)]
614 pub files: HashMap<PathBuf, PathBuf>,
615 #[serde(
622 deserialize_with = "de_macos_minimum_system_version",
623 default = "macos_minimum_system_version",
624 alias = "minimum-system-version"
625 )]
626 pub minimum_system_version: Option<String>,
627 #[serde(alias = "exception-domain")]
630 pub exception_domain: Option<String>,
631 #[serde(alias = "signing-identity")]
633 pub signing_identity: Option<String>,
634 #[serde(alias = "hardened-runtime", default = "default_true")]
638 pub hardened_runtime: bool,
639 #[serde(alias = "provider-short-name")]
641 pub provider_short_name: Option<String>,
642 pub entitlements: Option<String>,
644 #[serde(default)]
646 pub dmg: DmgConfig,
647}
648
649impl Default for MacConfig {
650 fn default() -> Self {
651 Self {
652 frameworks: None,
653 files: HashMap::new(),
654 minimum_system_version: macos_minimum_system_version(),
655 exception_domain: None,
656 signing_identity: None,
657 hardened_runtime: true,
658 provider_short_name: None,
659 entitlements: None,
660 dmg: Default::default(),
661 }
662 }
663}
664
665fn macos_minimum_system_version() -> Option<String> {
666 Some("10.13".into())
667}
668
669fn ios_minimum_system_version() -> String {
670 "13.0".into()
671}
672
673#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
677#[cfg_attr(feature = "schema", derive(JsonSchema))]
678#[serde(rename_all = "camelCase", deny_unknown_fields)]
679pub struct WixLanguageConfig {
680 #[serde(alias = "locale-path")]
682 pub locale_path: Option<String>,
683}
684
685#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
687#[cfg_attr(feature = "schema", derive(JsonSchema))]
688#[serde(untagged)]
689pub enum WixLanguage {
690 One(String),
692 List(Vec<String>),
694 Localized(HashMap<String, WixLanguageConfig>),
696}
697
698impl Default for WixLanguage {
699 fn default() -> Self {
700 Self::One("en-US".into())
701 }
702}
703
704#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
708#[cfg_attr(feature = "schema", derive(JsonSchema))]
709#[serde(rename_all = "camelCase", deny_unknown_fields)]
710pub struct WixConfig {
711 pub version: Option<String>,
720 #[serde(alias = "upgrade-code")]
729 pub upgrade_code: Option<uuid::Uuid>,
730 #[serde(default)]
732 pub language: WixLanguage,
733 pub template: Option<PathBuf>,
735 #[serde(default, alias = "fragment-paths")]
737 pub fragment_paths: Vec<PathBuf>,
738 #[serde(default, alias = "component-group-refs")]
740 pub component_group_refs: Vec<String>,
741 #[serde(default, alias = "component-refs")]
743 pub component_refs: Vec<String>,
744 #[serde(default, alias = "feature-group-refs")]
746 pub feature_group_refs: Vec<String>,
747 #[serde(default, alias = "feature-refs")]
749 pub feature_refs: Vec<String>,
750 #[serde(default, alias = "merge-refs")]
752 pub merge_refs: Vec<String>,
753 #[serde(default, alias = "enable-elevated-update-task")]
755 pub enable_elevated_update_task: bool,
756 #[serde(alias = "banner-path")]
761 pub banner_path: Option<PathBuf>,
762 #[serde(alias = "dialog-image-path")]
767 pub dialog_image_path: Option<PathBuf>,
768}
769
770#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
774#[cfg_attr(feature = "schema", derive(JsonSchema))]
775#[serde(rename_all = "camelCase", deny_unknown_fields)]
776pub enum NsisCompression {
777 Zlib,
779 Bzip2,
781 Lzma,
783 None,
785}
786
787impl Default for NsisCompression {
788 fn default() -> Self {
789 Self::Lzma
790 }
791}
792
793#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
795#[serde(rename_all = "camelCase", deny_unknown_fields)]
796#[cfg_attr(feature = "schema", derive(JsonSchema))]
797pub enum NSISInstallerMode {
798 CurrentUser,
804 PerMachine,
809 Both,
815}
816
817impl Default for NSISInstallerMode {
818 fn default() -> Self {
819 Self::CurrentUser
820 }
821}
822
823#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
825#[cfg_attr(feature = "schema", derive(JsonSchema))]
826#[serde(rename_all = "camelCase", deny_unknown_fields)]
827pub struct NsisConfig {
828 pub template: Option<PathBuf>,
830 #[serde(alias = "header-image")]
834 pub header_image: Option<PathBuf>,
835 #[serde(alias = "sidebar-image")]
839 pub sidebar_image: Option<PathBuf>,
840 #[serde(alias = "install-icon")]
842 pub installer_icon: Option<PathBuf>,
843 #[serde(default, alias = "install-mode")]
845 pub install_mode: NSISInstallerMode,
846 pub languages: Option<Vec<String>>,
852 pub custom_language_files: Option<HashMap<String, PathBuf>>,
859 #[serde(default, alias = "display-language-selector")]
862 pub display_language_selector: bool,
863 #[serde(default)]
867 pub compression: NsisCompression,
868 #[serde(alias = "start-menu-folder")]
877 pub start_menu_folder: Option<String>,
878 #[serde(alias = "installer-hooks")]
909 pub installer_hooks: Option<PathBuf>,
910 #[serde(alias = "minimum-webview2-version")]
914 pub minimum_webview2_version: Option<String>,
915}
916
917#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
922#[serde(tag = "type", rename_all = "camelCase", deny_unknown_fields)]
923#[cfg_attr(feature = "schema", derive(JsonSchema))]
924pub enum WebviewInstallMode {
925 Skip,
927 DownloadBootstrapper {
931 #[serde(default = "default_true")]
933 silent: bool,
934 },
935 EmbedBootstrapper {
939 #[serde(default = "default_true")]
941 silent: bool,
942 },
943 OfflineInstaller {
947 #[serde(default = "default_true")]
949 silent: bool,
950 },
951 FixedRuntime {
954 path: PathBuf,
959 },
960}
961
962impl Default for WebviewInstallMode {
963 fn default() -> Self {
964 Self::DownloadBootstrapper { silent: true }
965 }
966}
967
968#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
970#[cfg_attr(feature = "schema", derive(JsonSchema))]
971#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
972pub enum CustomSignCommandConfig {
973 Command(String),
982 CommandWithOptions {
987 cmd: String,
989 args: Vec<String>,
993 },
994}
995
996#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1000#[cfg_attr(feature = "schema", derive(JsonSchema))]
1001#[serde(rename_all = "camelCase", deny_unknown_fields)]
1002pub struct WindowsConfig {
1003 #[serde(alias = "digest-algorithm")]
1006 pub digest_algorithm: Option<String>,
1007 #[serde(alias = "certificate-thumbprint")]
1009 pub certificate_thumbprint: Option<String>,
1010 #[serde(alias = "timestamp-url")]
1012 pub timestamp_url: Option<String>,
1013 #[serde(default)]
1016 pub tsp: bool,
1017 #[serde(default, alias = "webview-install-mode")]
1019 pub webview_install_mode: WebviewInstallMode,
1020 #[serde(default = "default_true", alias = "allow-downgrades")]
1026 pub allow_downgrades: bool,
1027 pub wix: Option<WixConfig>,
1029 pub nsis: Option<NsisConfig>,
1031 #[serde(alias = "sign-command")]
1039 pub sign_command: Option<CustomSignCommandConfig>,
1040}
1041
1042impl Default for WindowsConfig {
1043 fn default() -> Self {
1044 Self {
1045 digest_algorithm: None,
1046 certificate_thumbprint: None,
1047 timestamp_url: None,
1048 tsp: false,
1049 webview_install_mode: Default::default(),
1050 allow_downgrades: true,
1051 wix: None,
1052 nsis: None,
1053 sign_command: None,
1054 }
1055 }
1056}
1057
1058#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1060#[cfg_attr(feature = "schema", derive(JsonSchema))]
1061pub enum BundleTypeRole {
1062 #[default]
1064 Editor,
1065 Viewer,
1067 Shell,
1069 QLGenerator,
1071 None,
1073}
1074
1075impl Display for BundleTypeRole {
1076 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1077 match self {
1078 Self::Editor => write!(f, "Editor"),
1079 Self::Viewer => write!(f, "Viewer"),
1080 Self::Shell => write!(f, "Shell"),
1081 Self::QLGenerator => write!(f, "QLGenerator"),
1082 Self::None => write!(f, "None"),
1083 }
1084 }
1085}
1086
1087#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
1091#[cfg_attr(feature = "schema", derive(JsonSchema))]
1092pub struct AssociationExt(pub String);
1093
1094impl fmt::Display for AssociationExt {
1095 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1096 write!(f, "{}", self.0)
1097 }
1098}
1099
1100impl<'d> serde::Deserialize<'d> for AssociationExt {
1101 fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {
1102 let ext = String::deserialize(deserializer)?;
1103 if let Some(ext) = ext.strip_prefix('.') {
1104 Ok(AssociationExt(ext.into()))
1105 } else {
1106 Ok(AssociationExt(ext))
1107 }
1108 }
1109}
1110
1111#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1113#[cfg_attr(feature = "schema", derive(JsonSchema))]
1114#[serde(rename_all = "camelCase", deny_unknown_fields)]
1115pub struct FileAssociation {
1116 pub ext: Vec<AssociationExt>,
1118 pub name: Option<String>,
1120 pub description: Option<String>,
1122 #[serde(default)]
1124 pub role: BundleTypeRole,
1125 #[serde(alias = "mime-type")]
1127 pub mime_type: Option<String>,
1128}
1129
1130#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1132#[cfg_attr(feature = "schema", derive(JsonSchema))]
1133#[serde(rename_all = "camelCase", deny_unknown_fields)]
1134pub struct DeepLinkProtocol {
1135 pub schemes: Vec<String>,
1137 pub name: Option<String>,
1139 #[serde(default)]
1141 pub role: BundleTypeRole,
1142}
1143
1144#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1147#[cfg_attr(feature = "schema", derive(JsonSchema))]
1148#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
1149pub enum BundleResources {
1150 List(Vec<String>),
1152 Map(HashMap<String, String>),
1154}
1155
1156impl BundleResources {
1157 pub fn push(&mut self, path: impl Into<String>) {
1159 match self {
1160 Self::List(l) => l.push(path.into()),
1161 Self::Map(l) => {
1162 let path = path.into();
1163 l.insert(path.clone(), path);
1164 }
1165 }
1166 }
1167}
1168
1169#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1171#[cfg_attr(feature = "schema", derive(JsonSchema))]
1172#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
1173pub enum Updater {
1174 String(V1Compatible),
1176 Bool(bool),
1179}
1180
1181impl Default for Updater {
1182 fn default() -> Self {
1183 Self::Bool(false)
1184 }
1185}
1186
1187#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1189#[cfg_attr(feature = "schema", derive(JsonSchema))]
1190#[serde(rename_all = "camelCase", deny_unknown_fields)]
1191pub enum V1Compatible {
1192 V1Compatible,
1194}
1195
1196#[skip_serializing_none]
1200#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1201#[cfg_attr(feature = "schema", derive(JsonSchema))]
1202#[serde(rename_all = "camelCase", deny_unknown_fields)]
1203pub struct BundleConfig {
1204 #[serde(default)]
1206 pub active: bool,
1207 #[serde(default)]
1209 pub targets: BundleTarget,
1210 #[serde(default)]
1211 pub create_updater_artifacts: Updater,
1213 pub publisher: Option<String>,
1218 pub homepage: Option<String>,
1223 #[serde(default)]
1225 pub icon: Vec<String>,
1226 pub resources: Option<BundleResources>,
1230 pub copyright: Option<String>,
1232 pub license: Option<String>,
1235 #[serde(alias = "license-file")]
1237 pub license_file: Option<PathBuf>,
1238 pub category: Option<String>,
1243 pub file_associations: Option<Vec<FileAssociation>>,
1245 #[serde(alias = "short-description")]
1247 pub short_description: Option<String>,
1248 #[serde(alias = "long-description")]
1250 pub long_description: Option<String>,
1251 #[serde(default, alias = "use-local-tools-dir")]
1259 pub use_local_tools_dir: bool,
1260 #[serde(alias = "external-bin")]
1272 pub external_bin: Option<Vec<String>>,
1273 #[serde(default)]
1275 pub windows: WindowsConfig,
1276 #[serde(default)]
1278 pub linux: LinuxConfig,
1279 #[serde(rename = "macOS", alias = "macos", default)]
1281 pub macos: MacConfig,
1282 #[serde(rename = "iOS", alias = "ios", default)]
1284 pub ios: IosConfig,
1285 #[serde(default)]
1287 pub android: AndroidConfig,
1288}
1289
1290#[derive(Debug, PartialEq, Eq, Serialize, Default, Clone, Copy)]
1292#[serde(rename_all = "camelCase", deny_unknown_fields)]
1293pub struct Color(pub u8, pub u8, pub u8, pub u8);
1294
1295impl From<Color> for (u8, u8, u8, u8) {
1296 fn from(value: Color) -> Self {
1297 (value.0, value.1, value.2, value.3)
1298 }
1299}
1300
1301impl From<Color> for (u8, u8, u8) {
1302 fn from(value: Color) -> Self {
1303 (value.0, value.1, value.2)
1304 }
1305}
1306
1307impl From<(u8, u8, u8, u8)> for Color {
1308 fn from(value: (u8, u8, u8, u8)) -> Self {
1309 Color(value.0, value.1, value.2, value.3)
1310 }
1311}
1312
1313impl From<(u8, u8, u8)> for Color {
1314 fn from(value: (u8, u8, u8)) -> Self {
1315 Color(value.0, value.1, value.2, 255)
1316 }
1317}
1318
1319impl From<Color> for [u8; 4] {
1320 fn from(value: Color) -> Self {
1321 [value.0, value.1, value.2, value.3]
1322 }
1323}
1324
1325impl From<Color> for [u8; 3] {
1326 fn from(value: Color) -> Self {
1327 [value.0, value.1, value.2]
1328 }
1329}
1330
1331impl From<[u8; 4]> for Color {
1332 fn from(value: [u8; 4]) -> Self {
1333 Color(value[0], value[1], value[2], value[3])
1334 }
1335}
1336
1337impl From<[u8; 3]> for Color {
1338 fn from(value: [u8; 3]) -> Self {
1339 Color(value[0], value[1], value[2], 255)
1340 }
1341}
1342
1343impl FromStr for Color {
1344 type Err = String;
1345 fn from_str(mut color: &str) -> Result<Self, Self::Err> {
1346 color = color.trim().strip_prefix('#').unwrap_or(color);
1347 let color = match color.len() {
1348 3 => color.chars()
1350 .flat_map(|c| std::iter::repeat(c).take(2))
1351 .chain(std::iter::repeat('f').take(2))
1352 .collect(),
1353 6 => format!("{color}FF"),
1354 8 => color.to_string(),
1355 _ => return Err("Invalid hex color length, must be either 3, 6 or 8, for example: #fff, #ffffff, or #ffffffff".into()),
1356 };
1357
1358 let r = u8::from_str_radix(&color[0..2], 16).map_err(|e| e.to_string())?;
1359 let g = u8::from_str_radix(&color[2..4], 16).map_err(|e| e.to_string())?;
1360 let b = u8::from_str_radix(&color[4..6], 16).map_err(|e| e.to_string())?;
1361 let a = u8::from_str_radix(&color[6..8], 16).map_err(|e| e.to_string())?;
1362
1363 Ok(Color(r, g, b, a))
1364 }
1365}
1366
1367fn default_alpha() -> u8 {
1368 255
1369}
1370
1371#[derive(Deserialize)]
1372#[cfg_attr(feature = "schema", derive(JsonSchema))]
1373#[serde(untagged)]
1374enum InnerColor {
1375 String(String),
1377 Rgb((u8, u8, u8)),
1379 Rgba((u8, u8, u8, u8)),
1381 RgbaObject {
1383 red: u8,
1384 green: u8,
1385 blue: u8,
1386 #[serde(default = "default_alpha")]
1387 alpha: u8,
1388 },
1389}
1390
1391impl<'de> Deserialize<'de> for Color {
1392 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1393 where
1394 D: Deserializer<'de>,
1395 {
1396 let color = InnerColor::deserialize(deserializer)?;
1397 let color = match color {
1398 InnerColor::String(string) => string.parse().map_err(serde::de::Error::custom)?,
1399 InnerColor::Rgb(rgb) => Color(rgb.0, rgb.1, rgb.2, 255),
1400 InnerColor::Rgba(rgb) => rgb.into(),
1401 InnerColor::RgbaObject {
1402 red,
1403 green,
1404 blue,
1405 alpha,
1406 } => Color(red, green, blue, alpha),
1407 };
1408
1409 Ok(color)
1410 }
1411}
1412
1413#[cfg(feature = "schema")]
1414impl schemars::JsonSchema for Color {
1415 fn schema_name() -> String {
1416 "Color".to_string()
1417 }
1418
1419 fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
1420 let mut schema = schemars::schema_for!(InnerColor).schema;
1421 schema.metadata = None; let any_of = schema.subschemas().any_of.as_mut().unwrap();
1425 let schemars::schema::Schema::Object(str_schema) = any_of.first_mut().unwrap() else {
1426 unreachable!()
1427 };
1428 str_schema.string().pattern = Some("^#?([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$".into());
1429
1430 schema.into()
1431 }
1432}
1433
1434#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1436#[cfg_attr(feature = "schema", derive(JsonSchema))]
1437#[serde(rename_all = "camelCase", deny_unknown_fields)]
1438pub enum BackgroundThrottlingPolicy {
1439 Disabled,
1441 Suspend,
1443 Throttle,
1445}
1446
1447#[skip_serializing_none]
1449#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default)]
1450#[cfg_attr(feature = "schema", derive(JsonSchema))]
1451#[serde(rename_all = "camelCase", deny_unknown_fields)]
1452pub struct WindowEffectsConfig {
1453 pub effects: Vec<WindowEffect>,
1456 pub state: Option<WindowEffectState>,
1458 pub radius: Option<f64>,
1460 pub color: Option<Color>,
1463}
1464
1465#[skip_serializing_none]
1469#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
1470#[cfg_attr(feature = "schema", derive(JsonSchema))]
1471#[serde(rename_all = "camelCase", deny_unknown_fields)]
1472pub struct WindowConfig {
1473 #[serde(default = "default_window_label")]
1475 pub label: String,
1476 #[serde(default = "default_true")]
1481 pub create: bool,
1482 #[serde(default)]
1484 pub url: WebviewUrl,
1485 #[serde(alias = "user-agent")]
1487 pub user_agent: Option<String>,
1488 #[serde(default = "default_true", alias = "drag-drop-enabled")]
1492 pub drag_drop_enabled: bool,
1493 #[serde(default)]
1495 pub center: bool,
1496 pub x: Option<f64>,
1498 pub y: Option<f64>,
1500 #[serde(default = "default_width")]
1502 pub width: f64,
1503 #[serde(default = "default_height")]
1505 pub height: f64,
1506 #[serde(alias = "min-width")]
1508 pub min_width: Option<f64>,
1509 #[serde(alias = "min-height")]
1511 pub min_height: Option<f64>,
1512 #[serde(alias = "max-width")]
1514 pub max_width: Option<f64>,
1515 #[serde(alias = "max-height")]
1517 pub max_height: Option<f64>,
1518 #[serde(default = "default_true")]
1520 pub resizable: bool,
1521 #[serde(default = "default_true")]
1529 pub maximizable: bool,
1530 #[serde(default = "default_true")]
1536 pub minimizable: bool,
1537 #[serde(default = "default_true")]
1545 pub closable: bool,
1546 #[serde(default = "default_title")]
1548 pub title: String,
1549 #[serde(default)]
1551 pub fullscreen: bool,
1552 #[serde(default = "default_true")]
1554 pub focus: bool,
1555 #[serde(default)]
1560 pub transparent: bool,
1561 #[serde(default)]
1563 pub maximized: bool,
1564 #[serde(default = "default_true")]
1566 pub visible: bool,
1567 #[serde(default = "default_true")]
1569 pub decorations: bool,
1570 #[serde(default, alias = "always-on-bottom")]
1572 pub always_on_bottom: bool,
1573 #[serde(default, alias = "always-on-top")]
1575 pub always_on_top: bool,
1576 #[serde(default, alias = "visible-on-all-workspaces")]
1582 pub visible_on_all_workspaces: bool,
1583 #[serde(default, alias = "content-protected")]
1585 pub content_protected: bool,
1586 #[serde(default, alias = "skip-taskbar")]
1588 pub skip_taskbar: bool,
1589 pub window_classname: Option<String>,
1591 pub theme: Option<crate::Theme>,
1593 #[serde(default, alias = "title-bar-style")]
1595 pub title_bar_style: TitleBarStyle,
1596 #[serde(default, alias = "traffic-light-position")]
1600 pub traffic_light_position: Option<LogicalPosition>,
1601 #[serde(default, alias = "hidden-title")]
1603 pub hidden_title: bool,
1604 #[serde(default, alias = "accept-first-mouse")]
1606 pub accept_first_mouse: bool,
1607 #[serde(default, alias = "tabbing-identifier")]
1614 pub tabbing_identifier: Option<String>,
1615 #[serde(default, alias = "additional-browser-args")]
1618 pub additional_browser_args: Option<String>,
1619 #[serde(default = "default_true")]
1629 pub shadow: bool,
1630 #[serde(default, alias = "window-effects")]
1639 pub window_effects: Option<WindowEffectsConfig>,
1640 #[serde(default)]
1646 pub incognito: bool,
1647 pub parent: Option<String>,
1659 #[serde(alias = "proxy-url")]
1667 pub proxy_url: Option<Url>,
1668 #[serde(default, alias = "zoom-hotkeys-enabled")]
1678 pub zoom_hotkeys_enabled: bool,
1679 #[serde(default, alias = "browser-extensions-enabled")]
1686 pub browser_extensions_enabled: bool,
1687
1688 #[serde(default, alias = "use-https-scheme")]
1698 pub use_https_scheme: bool,
1699 pub devtools: Option<bool>,
1709
1710 #[serde(alias = "background-color")]
1718 pub background_color: Option<Color>,
1719
1720 #[serde(default, alias = "background-throttling")]
1735 pub background_throttling: Option<BackgroundThrottlingPolicy>,
1736 #[serde(default, alias = "javascript-disabled")]
1738 pub javascript_disabled: bool,
1739}
1740
1741impl Default for WindowConfig {
1742 fn default() -> Self {
1743 Self {
1744 label: default_window_label(),
1745 url: WebviewUrl::default(),
1746 create: true,
1747 user_agent: None,
1748 drag_drop_enabled: true,
1749 center: false,
1750 x: None,
1751 y: None,
1752 width: default_width(),
1753 height: default_height(),
1754 min_width: None,
1755 min_height: None,
1756 max_width: None,
1757 max_height: None,
1758 resizable: true,
1759 maximizable: true,
1760 minimizable: true,
1761 closable: true,
1762 title: default_title(),
1763 fullscreen: false,
1764 focus: false,
1765 transparent: false,
1766 maximized: false,
1767 visible: true,
1768 decorations: true,
1769 always_on_bottom: false,
1770 always_on_top: false,
1771 visible_on_all_workspaces: false,
1772 content_protected: false,
1773 skip_taskbar: false,
1774 window_classname: None,
1775 theme: None,
1776 title_bar_style: Default::default(),
1777 traffic_light_position: None,
1778 hidden_title: false,
1779 accept_first_mouse: false,
1780 tabbing_identifier: None,
1781 additional_browser_args: None,
1782 shadow: true,
1783 window_effects: None,
1784 incognito: false,
1785 parent: None,
1786 proxy_url: None,
1787 zoom_hotkeys_enabled: false,
1788 browser_extensions_enabled: false,
1789 use_https_scheme: false,
1790 devtools: None,
1791 background_color: None,
1792 background_throttling: None,
1793 javascript_disabled: false,
1794 }
1795 }
1796}
1797
1798fn default_window_label() -> String {
1799 "main".to_string()
1800}
1801
1802fn default_width() -> f64 {
1803 800f64
1804}
1805
1806fn default_height() -> f64 {
1807 600f64
1808}
1809
1810fn default_title() -> String {
1811 "Tauri App".to_string()
1812}
1813
1814#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1817#[cfg_attr(feature = "schema", derive(JsonSchema))]
1818#[serde(rename_all = "camelCase", untagged)]
1819pub enum CspDirectiveSources {
1820 Inline(String),
1822 List(Vec<String>),
1824}
1825
1826impl Default for CspDirectiveSources {
1827 fn default() -> Self {
1828 Self::List(Vec::new())
1829 }
1830}
1831
1832impl From<CspDirectiveSources> for Vec<String> {
1833 fn from(sources: CspDirectiveSources) -> Self {
1834 match sources {
1835 CspDirectiveSources::Inline(source) => source.split(' ').map(|s| s.to_string()).collect(),
1836 CspDirectiveSources::List(l) => l,
1837 }
1838 }
1839}
1840
1841impl CspDirectiveSources {
1842 pub fn contains(&self, source: &str) -> bool {
1844 match self {
1845 Self::Inline(s) => s.contains(&format!("{source} ")) || s.contains(&format!(" {source}")),
1846 Self::List(l) => l.contains(&source.into()),
1847 }
1848 }
1849
1850 pub fn push<S: AsRef<str>>(&mut self, source: S) {
1852 match self {
1853 Self::Inline(s) => {
1854 s.push(' ');
1855 s.push_str(source.as_ref());
1856 }
1857 Self::List(l) => {
1858 l.push(source.as_ref().to_string());
1859 }
1860 }
1861 }
1862
1863 pub fn extend(&mut self, sources: Vec<String>) {
1865 for s in sources {
1866 self.push(s);
1867 }
1868 }
1869}
1870
1871#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1874#[cfg_attr(feature = "schema", derive(JsonSchema))]
1875#[serde(rename_all = "camelCase", untagged)]
1876pub enum Csp {
1877 Policy(String),
1879 DirectiveMap(HashMap<String, CspDirectiveSources>),
1881}
1882
1883impl From<HashMap<String, CspDirectiveSources>> for Csp {
1884 fn from(map: HashMap<String, CspDirectiveSources>) -> Self {
1885 Self::DirectiveMap(map)
1886 }
1887}
1888
1889impl From<Csp> for HashMap<String, CspDirectiveSources> {
1890 fn from(csp: Csp) -> Self {
1891 match csp {
1892 Csp::Policy(policy) => {
1893 let mut map = HashMap::new();
1894 for directive in policy.split(';') {
1895 let mut tokens = directive.trim().split(' ');
1896 if let Some(directive) = tokens.next() {
1897 let sources = tokens.map(|s| s.to_string()).collect::<Vec<String>>();
1898 map.insert(directive.to_string(), CspDirectiveSources::List(sources));
1899 }
1900 }
1901 map
1902 }
1903 Csp::DirectiveMap(m) => m,
1904 }
1905 }
1906}
1907
1908impl Display for Csp {
1909 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1910 match self {
1911 Self::Policy(s) => write!(f, "{s}"),
1912 Self::DirectiveMap(m) => {
1913 let len = m.len();
1914 let mut i = 0;
1915 for (directive, sources) in m {
1916 let sources: Vec<String> = sources.clone().into();
1917 write!(f, "{} {}", directive, sources.join(" "))?;
1918 i += 1;
1919 if i != len {
1920 write!(f, "; ")?;
1921 }
1922 }
1923 Ok(())
1924 }
1925 }
1926 }
1927}
1928
1929#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1931#[serde(untagged)]
1932#[cfg_attr(feature = "schema", derive(JsonSchema))]
1933pub enum DisabledCspModificationKind {
1934 Flag(bool),
1937 List(Vec<String>),
1939}
1940
1941impl DisabledCspModificationKind {
1942 pub fn can_modify(&self, directive: &str) -> bool {
1944 match self {
1945 Self::Flag(f) => !f,
1946 Self::List(l) => !l.contains(&directive.into()),
1947 }
1948 }
1949}
1950
1951impl Default for DisabledCspModificationKind {
1952 fn default() -> Self {
1953 Self::Flag(false)
1954 }
1955}
1956
1957#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1966#[serde(untagged)]
1967#[cfg_attr(feature = "schema", derive(JsonSchema))]
1968pub enum FsScope {
1969 AllowedPaths(Vec<PathBuf>),
1971 #[serde(rename_all = "camelCase")]
1973 Scope {
1974 #[serde(default)]
1976 allow: Vec<PathBuf>,
1977 #[serde(default)]
1980 deny: Vec<PathBuf>,
1981 #[serde(alias = "require-literal-leading-dot")]
1990 require_literal_leading_dot: Option<bool>,
1991 },
1992}
1993
1994impl Default for FsScope {
1995 fn default() -> Self {
1996 Self::AllowedPaths(Vec::new())
1997 }
1998}
1999
2000impl FsScope {
2001 pub fn allowed_paths(&self) -> &Vec<PathBuf> {
2003 match self {
2004 Self::AllowedPaths(p) => p,
2005 Self::Scope { allow, .. } => allow,
2006 }
2007 }
2008
2009 pub fn forbidden_paths(&self) -> Option<&Vec<PathBuf>> {
2011 match self {
2012 Self::AllowedPaths(_) => None,
2013 Self::Scope { deny, .. } => Some(deny),
2014 }
2015 }
2016}
2017
2018#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2022#[cfg_attr(feature = "schema", derive(JsonSchema))]
2023#[serde(rename_all = "camelCase", deny_unknown_fields)]
2024pub struct AssetProtocolConfig {
2025 #[serde(default)]
2027 pub scope: FsScope,
2028 #[serde(default)]
2030 pub enable: bool,
2031}
2032
2033#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2037#[cfg_attr(feature = "schema", derive(JsonSchema))]
2038#[serde(rename_all = "camelCase", untagged)]
2039pub enum HeaderSource {
2040 Inline(String),
2042 List(Vec<String>),
2044 Map(HashMap<String, String>),
2046}
2047
2048impl Display for HeaderSource {
2049 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2050 match self {
2051 Self::Inline(s) => write!(f, "{s}"),
2052 Self::List(l) => write!(f, "{}", l.join(", ")),
2053 Self::Map(m) => {
2054 let len = m.len();
2055 let mut i = 0;
2056 for (key, value) in m {
2057 write!(f, "{} {}", key, value)?;
2058 i += 1;
2059 if i != len {
2060 write!(f, "; ")?;
2061 }
2062 }
2063 Ok(())
2064 }
2065 }
2066 }
2067}
2068
2069pub trait HeaderAddition {
2073 fn add_configured_headers(self, headers: Option<&HeaderConfig>) -> http::response::Builder;
2075}
2076
2077impl HeaderAddition for Builder {
2078 fn add_configured_headers(mut self, headers: Option<&HeaderConfig>) -> http::response::Builder {
2082 if let Some(headers) = headers {
2083 if let Some(value) = &headers.access_control_allow_credentials {
2085 self = self.header("Access-Control-Allow-Credentials", value.to_string());
2086 };
2087
2088 if let Some(value) = &headers.access_control_allow_headers {
2090 self = self.header("Access-Control-Allow-Headers", value.to_string());
2091 };
2092
2093 if let Some(value) = &headers.access_control_allow_methods {
2095 self = self.header("Access-Control-Allow-Methods", value.to_string());
2096 };
2097
2098 if let Some(value) = &headers.access_control_expose_headers {
2100 self = self.header("Access-Control-Expose-Headers", value.to_string());
2101 };
2102
2103 if let Some(value) = &headers.access_control_max_age {
2105 self = self.header("Access-Control-Max-Age", value.to_string());
2106 };
2107
2108 if let Some(value) = &headers.cross_origin_embedder_policy {
2110 self = self.header("Cross-Origin-Embedder-Policy", value.to_string());
2111 };
2112
2113 if let Some(value) = &headers.cross_origin_opener_policy {
2115 self = self.header("Cross-Origin-Opener-Policy", value.to_string());
2116 };
2117
2118 if let Some(value) = &headers.cross_origin_resource_policy {
2120 self = self.header("Cross-Origin-Resource-Policy", value.to_string());
2121 };
2122
2123 if let Some(value) = &headers.permissions_policy {
2125 self = self.header("Permission-Policy", value.to_string());
2126 };
2127
2128 if let Some(value) = &headers.timing_allow_origin {
2130 self = self.header("Timing-Allow-Origin", value.to_string());
2131 };
2132
2133 if let Some(value) = &headers.x_content_type_options {
2135 self = self.header("X-Content-Type-Options", value.to_string());
2136 };
2137
2138 if let Some(value) = &headers.tauri_custom_header {
2140 self = self.header("Tauri-Custom-Header", value.to_string());
2142 };
2143 }
2144 self
2145 }
2146}
2147
2148#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2199#[cfg_attr(feature = "schema", derive(JsonSchema))]
2200#[serde(deny_unknown_fields)]
2201pub struct HeaderConfig {
2202 #[serde(rename = "Access-Control-Allow-Credentials")]
2207 pub access_control_allow_credentials: Option<HeaderSource>,
2208 #[serde(rename = "Access-Control-Allow-Headers")]
2216 pub access_control_allow_headers: Option<HeaderSource>,
2217 #[serde(rename = "Access-Control-Allow-Methods")]
2222 pub access_control_allow_methods: Option<HeaderSource>,
2223 #[serde(rename = "Access-Control-Expose-Headers")]
2229 pub access_control_expose_headers: Option<HeaderSource>,
2230 #[serde(rename = "Access-Control-Max-Age")]
2237 pub access_control_max_age: Option<HeaderSource>,
2238 #[serde(rename = "Cross-Origin-Embedder-Policy")]
2243 pub cross_origin_embedder_policy: Option<HeaderSource>,
2244 #[serde(rename = "Cross-Origin-Opener-Policy")]
2251 pub cross_origin_opener_policy: Option<HeaderSource>,
2252 #[serde(rename = "Cross-Origin-Resource-Policy")]
2257 pub cross_origin_resource_policy: Option<HeaderSource>,
2258 #[serde(rename = "Permissions-Policy")]
2263 pub permissions_policy: Option<HeaderSource>,
2264 #[serde(rename = "Timing-Allow-Origin")]
2270 pub timing_allow_origin: Option<HeaderSource>,
2271 #[serde(rename = "X-Content-Type-Options")]
2278 pub x_content_type_options: Option<HeaderSource>,
2279 #[serde(rename = "Tauri-Custom-Header")]
2284 pub tauri_custom_header: Option<HeaderSource>,
2285}
2286
2287impl HeaderConfig {
2288 pub fn new() -> Self {
2290 HeaderConfig {
2291 access_control_allow_credentials: None,
2292 access_control_allow_methods: None,
2293 access_control_allow_headers: None,
2294 access_control_expose_headers: None,
2295 access_control_max_age: None,
2296 cross_origin_embedder_policy: None,
2297 cross_origin_opener_policy: None,
2298 cross_origin_resource_policy: None,
2299 permissions_policy: None,
2300 timing_allow_origin: None,
2301 x_content_type_options: None,
2302 tauri_custom_header: None,
2303 }
2304 }
2305}
2306
2307#[skip_serializing_none]
2311#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
2312#[cfg_attr(feature = "schema", derive(JsonSchema))]
2313#[serde(rename_all = "camelCase", deny_unknown_fields)]
2314pub struct SecurityConfig {
2315 pub csp: Option<Csp>,
2321 #[serde(alias = "dev-csp")]
2326 pub dev_csp: Option<Csp>,
2327 #[serde(default, alias = "freeze-prototype")]
2329 pub freeze_prototype: bool,
2330 #[serde(default, alias = "dangerous-disable-asset-csp-modification")]
2343 pub dangerous_disable_asset_csp_modification: DisabledCspModificationKind,
2344 #[serde(default, alias = "asset-protocol")]
2346 pub asset_protocol: AssetProtocolConfig,
2347 #[serde(default)]
2349 pub pattern: PatternKind,
2350 #[serde(default)]
2354 pub capabilities: Vec<CapabilityEntry>,
2355 #[serde(default)]
2358 pub headers: Option<HeaderConfig>,
2359}
2360
2361#[derive(Debug, Clone, PartialEq, Serialize)]
2363#[cfg_attr(feature = "schema", derive(JsonSchema))]
2364#[serde(untagged)]
2365pub enum CapabilityEntry {
2366 Inlined(Capability),
2368 Reference(String),
2370}
2371
2372impl<'de> Deserialize<'de> for CapabilityEntry {
2373 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2374 where
2375 D: Deserializer<'de>,
2376 {
2377 UntaggedEnumVisitor::new()
2378 .string(|string| Ok(Self::Reference(string.to_owned())))
2379 .map(|map| map.deserialize::<Capability>().map(Self::Inlined))
2380 .deserialize(deserializer)
2381 }
2382}
2383
2384#[skip_serializing_none]
2386#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
2387#[serde(rename_all = "lowercase", tag = "use", content = "options")]
2388#[cfg_attr(feature = "schema", derive(JsonSchema))]
2389pub enum PatternKind {
2390 Brownfield,
2392 Isolation {
2394 dir: PathBuf,
2396 },
2397}
2398
2399impl Default for PatternKind {
2400 fn default() -> Self {
2401 Self::Brownfield
2402 }
2403}
2404
2405#[skip_serializing_none]
2409#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
2410#[cfg_attr(feature = "schema", derive(JsonSchema))]
2411#[serde(rename_all = "camelCase", deny_unknown_fields)]
2412pub struct AppConfig {
2413 #[serde(default)]
2415 pub windows: Vec<WindowConfig>,
2416 #[serde(default)]
2418 pub security: SecurityConfig,
2419 #[serde(alias = "tray-icon")]
2421 pub tray_icon: Option<TrayIconConfig>,
2422 #[serde(rename = "macOSPrivateApi", alias = "macos-private-api", default)]
2424 pub macos_private_api: bool,
2425 #[serde(default, alias = "with-global-tauri")]
2427 pub with_global_tauri: bool,
2428 #[serde(rename = "enableGTKAppId", alias = "enable-gtk-app-id", default)]
2430 pub enable_gtk_app_id: bool,
2431}
2432
2433impl AppConfig {
2434 pub fn all_features() -> Vec<&'static str> {
2436 vec![
2437 "tray-icon",
2438 "macos-private-api",
2439 "protocol-asset",
2440 "isolation",
2441 ]
2442 }
2443
2444 pub fn features(&self) -> Vec<&str> {
2446 let mut features = Vec::new();
2447 if self.tray_icon.is_some() {
2448 features.push("tray-icon");
2449 }
2450 if self.macos_private_api {
2451 features.push("macos-private-api");
2452 }
2453 if self.security.asset_protocol.enable {
2454 features.push("protocol-asset");
2455 }
2456
2457 if let PatternKind::Isolation { .. } = self.security.pattern {
2458 features.push("isolation");
2459 }
2460
2461 features.sort_unstable();
2462 features
2463 }
2464}
2465
2466#[skip_serializing_none]
2470#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2471#[cfg_attr(feature = "schema", derive(JsonSchema))]
2472#[serde(rename_all = "camelCase", deny_unknown_fields)]
2473pub struct TrayIconConfig {
2474 pub id: Option<String>,
2476 #[serde(alias = "icon-path")]
2482 pub icon_path: PathBuf,
2483 #[serde(default, alias = "icon-as-template")]
2485 pub icon_as_template: bool,
2486 #[serde(default = "default_true", alias = "menu-on-left-click")]
2492 #[deprecated(since = "2.2.0", note = "Use `show_menu_on_left_click` instead.")]
2493 pub menu_on_left_click: bool,
2494 #[serde(default = "default_true", alias = "show-menu-on-left-click")]
2500 pub show_menu_on_left_click: bool,
2501 pub title: Option<String>,
2503 pub tooltip: Option<String>,
2505}
2506
2507#[skip_serializing_none]
2509#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2510#[cfg_attr(feature = "schema", derive(JsonSchema))]
2511#[serde(rename_all = "camelCase", deny_unknown_fields)]
2512pub struct IosConfig {
2513 pub template: Option<PathBuf>,
2517 pub frameworks: Option<Vec<String>>,
2521 #[serde(alias = "development-team")]
2524 pub development_team: Option<String>,
2525 #[serde(
2529 alias = "minimum-system-version",
2530 default = "ios_minimum_system_version"
2531 )]
2532 pub minimum_system_version: String,
2533}
2534
2535impl Default for IosConfig {
2536 fn default() -> Self {
2537 Self {
2538 template: None,
2539 frameworks: None,
2540 development_team: None,
2541 minimum_system_version: ios_minimum_system_version(),
2542 }
2543 }
2544}
2545
2546#[skip_serializing_none]
2548#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2549#[cfg_attr(feature = "schema", derive(JsonSchema))]
2550#[serde(rename_all = "camelCase", deny_unknown_fields)]
2551pub struct AndroidConfig {
2552 #[serde(alias = "min-sdk-version", default = "default_min_sdk_version")]
2555 pub min_sdk_version: u32,
2556
2557 #[serde(alias = "version-code")]
2563 #[cfg_attr(feature = "schema", validate(range(min = 1, max = 2_100_000_000)))]
2564 pub version_code: Option<u32>,
2565}
2566
2567impl Default for AndroidConfig {
2568 fn default() -> Self {
2569 Self {
2570 min_sdk_version: default_min_sdk_version(),
2571 version_code: None,
2572 }
2573 }
2574}
2575
2576fn default_min_sdk_version() -> u32 {
2577 24
2578}
2579
2580#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2582#[cfg_attr(feature = "schema", derive(JsonSchema))]
2583#[serde(untagged, deny_unknown_fields)]
2584#[non_exhaustive]
2585pub enum FrontendDist {
2586 Url(Url),
2588 Directory(PathBuf),
2590 Files(Vec<PathBuf>),
2592}
2593
2594impl std::fmt::Display for FrontendDist {
2595 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2596 match self {
2597 Self::Url(url) => write!(f, "{url}"),
2598 Self::Directory(p) => write!(f, "{}", p.display()),
2599 Self::Files(files) => write!(f, "{}", serde_json::to_string(files).unwrap()),
2600 }
2601 }
2602}
2603
2604#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2606#[cfg_attr(feature = "schema", derive(JsonSchema))]
2607#[serde(rename_all = "camelCase", untagged)]
2608pub enum BeforeDevCommand {
2609 Script(String),
2611 ScriptWithOptions {
2613 script: String,
2615 cwd: Option<String>,
2617 #[serde(default)]
2619 wait: bool,
2620 },
2621}
2622
2623#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2625#[cfg_attr(feature = "schema", derive(JsonSchema))]
2626#[serde(rename_all = "camelCase", untagged)]
2627pub enum HookCommand {
2628 Script(String),
2630 ScriptWithOptions {
2632 script: String,
2634 cwd: Option<String>,
2636 },
2637}
2638
2639#[skip_serializing_none]
2643#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Default)]
2644#[cfg_attr(feature = "schema", derive(JsonSchema))]
2645#[serde(rename_all = "camelCase", deny_unknown_fields)]
2646pub struct BuildConfig {
2647 pub runner: Option<String>,
2649 #[serde(alias = "dev-url")]
2657 pub dev_url: Option<Url>,
2658 #[serde(alias = "frontend-dist")]
2672 pub frontend_dist: Option<FrontendDist>,
2673 #[serde(alias = "before-dev-command")]
2677 pub before_dev_command: Option<BeforeDevCommand>,
2678 #[serde(alias = "before-build-command")]
2682 pub before_build_command: Option<HookCommand>,
2683 #[serde(alias = "before-bundle-command")]
2687 pub before_bundle_command: Option<HookCommand>,
2688 pub features: Option<Vec<String>>,
2690 #[serde(alias = "remove-unused-commands", default)]
2698 pub remove_unused_commands: bool,
2699}
2700
2701#[derive(Debug, PartialEq, Eq)]
2702struct PackageVersion(String);
2703
2704impl<'d> serde::Deserialize<'d> for PackageVersion {
2705 fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {
2706 struct PackageVersionVisitor;
2707
2708 impl Visitor<'_> for PackageVersionVisitor {
2709 type Value = PackageVersion;
2710
2711 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2712 write!(
2713 formatter,
2714 "a semver string or a path to a package.json file"
2715 )
2716 }
2717
2718 fn visit_str<E: DeError>(self, value: &str) -> Result<PackageVersion, E> {
2719 let path = PathBuf::from(value);
2720 if path.exists() {
2721 let json_str = read_to_string(&path)
2722 .map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?;
2723 let package_json: serde_json::Value = serde_json::from_str(&json_str)
2724 .map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?;
2725 if let Some(obj) = package_json.as_object() {
2726 let version = obj
2727 .get("version")
2728 .ok_or_else(|| DeError::custom("JSON must contain a `version` field"))?
2729 .as_str()
2730 .ok_or_else(|| {
2731 DeError::custom(format!("`{} > version` must be a string", path.display()))
2732 })?;
2733 Ok(PackageVersion(
2734 Version::from_str(version)
2735 .map_err(|_| DeError::custom("`package > version` must be a semver string"))?
2736 .to_string(),
2737 ))
2738 } else {
2739 Err(DeError::custom(
2740 "`package > version` value is not a path to a JSON object",
2741 ))
2742 }
2743 } else {
2744 Ok(PackageVersion(
2745 Version::from_str(value)
2746 .map_err(|_| DeError::custom("`package > version` must be a semver string"))?
2747 .to_string(),
2748 ))
2749 }
2750 }
2751 }
2752
2753 deserializer.deserialize_string(PackageVersionVisitor {})
2754 }
2755}
2756
2757fn version_deserializer<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
2758where
2759 D: Deserializer<'de>,
2760{
2761 Option::<PackageVersion>::deserialize(deserializer).map(|v| v.map(|v| v.0))
2762}
2763
2764#[skip_serializing_none]
2830#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
2831#[cfg_attr(feature = "schema", derive(JsonSchema))]
2832#[serde(rename_all = "camelCase", deny_unknown_fields)]
2833pub struct Config {
2834 #[serde(rename = "$schema")]
2836 pub schema: Option<String>,
2837 #[serde(alias = "product-name")]
2839 #[cfg_attr(feature = "schema", validate(regex(pattern = "^[^/\\:*?\"<>|]+$")))]
2840 pub product_name: Option<String>,
2841 #[serde(alias = "main-binary-name")]
2843 pub main_binary_name: Option<String>,
2844 #[serde(deserialize_with = "version_deserializer", default)]
2848 pub version: Option<String>,
2849 pub identifier: String,
2855 #[serde(default)]
2857 pub app: AppConfig,
2858 #[serde(default)]
2860 pub build: BuildConfig,
2861 #[serde(default)]
2863 pub bundle: BundleConfig,
2864 #[serde(default)]
2866 pub plugins: PluginConfig,
2867}
2868
2869#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
2873#[cfg_attr(feature = "schema", derive(JsonSchema))]
2874pub struct PluginConfig(pub HashMap<String, JsonValue>);
2875
2876#[cfg(feature = "build")]
2882mod build {
2883 use super::*;
2884 use crate::{literal_struct, tokens::*};
2885 use proc_macro2::TokenStream;
2886 use quote::{quote, ToTokens, TokenStreamExt};
2887 use std::convert::identity;
2888
2889 impl ToTokens for WebviewUrl {
2890 fn to_tokens(&self, tokens: &mut TokenStream) {
2891 let prefix = quote! { ::tauri::utils::config::WebviewUrl };
2892
2893 tokens.append_all(match self {
2894 Self::App(path) => {
2895 let path = path_buf_lit(path);
2896 quote! { #prefix::App(#path) }
2897 }
2898 Self::External(url) => {
2899 let url = url_lit(url);
2900 quote! { #prefix::External(#url) }
2901 }
2902 Self::CustomProtocol(url) => {
2903 let url = url_lit(url);
2904 quote! { #prefix::CustomProtocol(#url) }
2905 }
2906 })
2907 }
2908 }
2909
2910 impl ToTokens for BackgroundThrottlingPolicy {
2911 fn to_tokens(&self, tokens: &mut TokenStream) {
2912 let prefix = quote! { ::tauri::utils::config::BackgroundThrottlingPolicy };
2913 tokens.append_all(match self {
2914 Self::Disabled => quote! { #prefix::Disabled },
2915 Self::Throttle => quote! { #prefix::Throttle },
2916 Self::Suspend => quote! { #prefix::Suspend },
2917 })
2918 }
2919 }
2920
2921 impl ToTokens for crate::Theme {
2922 fn to_tokens(&self, tokens: &mut TokenStream) {
2923 let prefix = quote! { ::tauri::utils::Theme };
2924
2925 tokens.append_all(match self {
2926 Self::Light => quote! { #prefix::Light },
2927 Self::Dark => quote! { #prefix::Dark },
2928 })
2929 }
2930 }
2931
2932 impl ToTokens for Color {
2933 fn to_tokens(&self, tokens: &mut TokenStream) {
2934 let Color(r, g, b, a) = self;
2935 tokens.append_all(quote! {::tauri::utils::config::Color(#r,#g,#b,#a)});
2936 }
2937 }
2938 impl ToTokens for WindowEffectsConfig {
2939 fn to_tokens(&self, tokens: &mut TokenStream) {
2940 let effects = vec_lit(self.effects.clone(), |d| d);
2941 let state = opt_lit(self.state.as_ref());
2942 let radius = opt_lit(self.radius.as_ref());
2943 let color = opt_lit(self.color.as_ref());
2944
2945 literal_struct!(
2946 tokens,
2947 ::tauri::utils::config::WindowEffectsConfig,
2948 effects,
2949 state,
2950 radius,
2951 color
2952 )
2953 }
2954 }
2955
2956 impl ToTokens for crate::TitleBarStyle {
2957 fn to_tokens(&self, tokens: &mut TokenStream) {
2958 let prefix = quote! { ::tauri::utils::TitleBarStyle };
2959
2960 tokens.append_all(match self {
2961 Self::Visible => quote! { #prefix::Visible },
2962 Self::Transparent => quote! { #prefix::Transparent },
2963 Self::Overlay => quote! { #prefix::Overlay },
2964 })
2965 }
2966 }
2967
2968 impl ToTokens for LogicalPosition {
2969 fn to_tokens(&self, tokens: &mut TokenStream) {
2970 let LogicalPosition { x, y } = self;
2971 literal_struct!(tokens, ::tauri::utils::config::LogicalPosition, x, y)
2972 }
2973 }
2974
2975 impl ToTokens for crate::WindowEffect {
2976 fn to_tokens(&self, tokens: &mut TokenStream) {
2977 let prefix = quote! { ::tauri::utils::WindowEffect };
2978
2979 #[allow(deprecated)]
2980 tokens.append_all(match self {
2981 WindowEffect::AppearanceBased => quote! { #prefix::AppearanceBased},
2982 WindowEffect::Light => quote! { #prefix::Light},
2983 WindowEffect::Dark => quote! { #prefix::Dark},
2984 WindowEffect::MediumLight => quote! { #prefix::MediumLight},
2985 WindowEffect::UltraDark => quote! { #prefix::UltraDark},
2986 WindowEffect::Titlebar => quote! { #prefix::Titlebar},
2987 WindowEffect::Selection => quote! { #prefix::Selection},
2988 WindowEffect::Menu => quote! { #prefix::Menu},
2989 WindowEffect::Popover => quote! { #prefix::Popover},
2990 WindowEffect::Sidebar => quote! { #prefix::Sidebar},
2991 WindowEffect::HeaderView => quote! { #prefix::HeaderView},
2992 WindowEffect::Sheet => quote! { #prefix::Sheet},
2993 WindowEffect::WindowBackground => quote! { #prefix::WindowBackground},
2994 WindowEffect::HudWindow => quote! { #prefix::HudWindow},
2995 WindowEffect::FullScreenUI => quote! { #prefix::FullScreenUI},
2996 WindowEffect::Tooltip => quote! { #prefix::Tooltip},
2997 WindowEffect::ContentBackground => quote! { #prefix::ContentBackground},
2998 WindowEffect::UnderWindowBackground => quote! { #prefix::UnderWindowBackground},
2999 WindowEffect::UnderPageBackground => quote! { #prefix::UnderPageBackground},
3000 WindowEffect::Mica => quote! { #prefix::Mica},
3001 WindowEffect::MicaDark => quote! { #prefix::MicaDark},
3002 WindowEffect::MicaLight => quote! { #prefix::MicaLight},
3003 WindowEffect::Blur => quote! { #prefix::Blur},
3004 WindowEffect::Acrylic => quote! { #prefix::Acrylic},
3005 WindowEffect::Tabbed => quote! { #prefix::Tabbed },
3006 WindowEffect::TabbedDark => quote! { #prefix::TabbedDark },
3007 WindowEffect::TabbedLight => quote! { #prefix::TabbedLight },
3008 })
3009 }
3010 }
3011
3012 impl ToTokens for crate::WindowEffectState {
3013 fn to_tokens(&self, tokens: &mut TokenStream) {
3014 let prefix = quote! { ::tauri::utils::WindowEffectState };
3015
3016 #[allow(deprecated)]
3017 tokens.append_all(match self {
3018 WindowEffectState::Active => quote! { #prefix::Active},
3019 WindowEffectState::FollowsWindowActiveState => quote! { #prefix::FollowsWindowActiveState},
3020 WindowEffectState::Inactive => quote! { #prefix::Inactive},
3021 })
3022 }
3023 }
3024
3025 impl ToTokens for WindowConfig {
3026 fn to_tokens(&self, tokens: &mut TokenStream) {
3027 let label = str_lit(&self.label);
3028 let create = &self.create;
3029 let url = &self.url;
3030 let user_agent = opt_str_lit(self.user_agent.as_ref());
3031 let drag_drop_enabled = self.drag_drop_enabled;
3032 let center = self.center;
3033 let x = opt_lit(self.x.as_ref());
3034 let y = opt_lit(self.y.as_ref());
3035 let width = self.width;
3036 let height = self.height;
3037 let min_width = opt_lit(self.min_width.as_ref());
3038 let min_height = opt_lit(self.min_height.as_ref());
3039 let max_width = opt_lit(self.max_width.as_ref());
3040 let max_height = opt_lit(self.max_height.as_ref());
3041 let resizable = self.resizable;
3042 let maximizable = self.maximizable;
3043 let minimizable = self.minimizable;
3044 let closable = self.closable;
3045 let title = str_lit(&self.title);
3046 let proxy_url = opt_lit(self.proxy_url.as_ref().map(url_lit).as_ref());
3047 let fullscreen = self.fullscreen;
3048 let focus = self.focus;
3049 let transparent = self.transparent;
3050 let maximized = self.maximized;
3051 let visible = self.visible;
3052 let decorations = self.decorations;
3053 let always_on_bottom = self.always_on_bottom;
3054 let always_on_top = self.always_on_top;
3055 let visible_on_all_workspaces = self.visible_on_all_workspaces;
3056 let content_protected = self.content_protected;
3057 let skip_taskbar = self.skip_taskbar;
3058 let window_classname = opt_str_lit(self.window_classname.as_ref());
3059 let theme = opt_lit(self.theme.as_ref());
3060 let title_bar_style = &self.title_bar_style;
3061 let traffic_light_position = opt_lit(self.traffic_light_position.as_ref());
3062 let hidden_title = self.hidden_title;
3063 let accept_first_mouse = self.accept_first_mouse;
3064 let tabbing_identifier = opt_str_lit(self.tabbing_identifier.as_ref());
3065 let additional_browser_args = opt_str_lit(self.additional_browser_args.as_ref());
3066 let shadow = self.shadow;
3067 let window_effects = opt_lit(self.window_effects.as_ref());
3068 let incognito = self.incognito;
3069 let parent = opt_str_lit(self.parent.as_ref());
3070 let zoom_hotkeys_enabled = self.zoom_hotkeys_enabled;
3071 let browser_extensions_enabled = self.browser_extensions_enabled;
3072 let use_https_scheme = self.use_https_scheme;
3073 let devtools = opt_lit(self.devtools.as_ref());
3074 let background_color = opt_lit(self.background_color.as_ref());
3075 let background_throttling = opt_lit(self.background_throttling.as_ref());
3076 let javascript_disabled = self.javascript_disabled;
3077
3078 literal_struct!(
3079 tokens,
3080 ::tauri::utils::config::WindowConfig,
3081 label,
3082 url,
3083 create,
3084 user_agent,
3085 drag_drop_enabled,
3086 center,
3087 x,
3088 y,
3089 width,
3090 height,
3091 min_width,
3092 min_height,
3093 max_width,
3094 max_height,
3095 resizable,
3096 maximizable,
3097 minimizable,
3098 closable,
3099 title,
3100 proxy_url,
3101 fullscreen,
3102 focus,
3103 transparent,
3104 maximized,
3105 visible,
3106 decorations,
3107 always_on_bottom,
3108 always_on_top,
3109 visible_on_all_workspaces,
3110 content_protected,
3111 skip_taskbar,
3112 window_classname,
3113 theme,
3114 title_bar_style,
3115 traffic_light_position,
3116 hidden_title,
3117 accept_first_mouse,
3118 tabbing_identifier,
3119 additional_browser_args,
3120 shadow,
3121 window_effects,
3122 incognito,
3123 parent,
3124 zoom_hotkeys_enabled,
3125 browser_extensions_enabled,
3126 use_https_scheme,
3127 devtools,
3128 background_color,
3129 background_throttling,
3130 javascript_disabled
3131 );
3132 }
3133 }
3134
3135 impl ToTokens for PatternKind {
3136 fn to_tokens(&self, tokens: &mut TokenStream) {
3137 let prefix = quote! { ::tauri::utils::config::PatternKind };
3138
3139 tokens.append_all(match self {
3140 Self::Brownfield => quote! { #prefix::Brownfield },
3141 #[cfg(not(feature = "isolation"))]
3142 Self::Isolation { dir: _ } => quote! { #prefix::Brownfield },
3143 #[cfg(feature = "isolation")]
3144 Self::Isolation { dir } => {
3145 let dir = path_buf_lit(dir);
3146 quote! { #prefix::Isolation { dir: #dir } }
3147 }
3148 })
3149 }
3150 }
3151
3152 impl ToTokens for WebviewInstallMode {
3153 fn to_tokens(&self, tokens: &mut TokenStream) {
3154 let prefix = quote! { ::tauri::utils::config::WebviewInstallMode };
3155
3156 tokens.append_all(match self {
3157 Self::Skip => quote! { #prefix::Skip },
3158 Self::DownloadBootstrapper { silent } => {
3159 quote! { #prefix::DownloadBootstrapper { silent: #silent } }
3160 }
3161 Self::EmbedBootstrapper { silent } => {
3162 quote! { #prefix::EmbedBootstrapper { silent: #silent } }
3163 }
3164 Self::OfflineInstaller { silent } => {
3165 quote! { #prefix::OfflineInstaller { silent: #silent } }
3166 }
3167 Self::FixedRuntime { path } => {
3168 let path = path_buf_lit(path);
3169 quote! { #prefix::FixedRuntime { path: #path } }
3170 }
3171 })
3172 }
3173 }
3174
3175 impl ToTokens for WindowsConfig {
3176 fn to_tokens(&self, tokens: &mut TokenStream) {
3177 let webview_install_mode = &self.webview_install_mode;
3178 tokens.append_all(quote! { ::tauri::utils::config::WindowsConfig {
3179 webview_install_mode: #webview_install_mode,
3180 ..Default::default()
3181 }})
3182 }
3183 }
3184
3185 impl ToTokens for BundleConfig {
3186 fn to_tokens(&self, tokens: &mut TokenStream) {
3187 let publisher = quote!(None);
3188 let homepage = quote!(None);
3189 let icon = vec_lit(&self.icon, str_lit);
3190 let active = self.active;
3191 let targets = quote!(Default::default());
3192 let create_updater_artifacts = quote!(Default::default());
3193 let resources = quote!(None);
3194 let copyright = quote!(None);
3195 let category = quote!(None);
3196 let file_associations = quote!(None);
3197 let short_description = quote!(None);
3198 let long_description = quote!(None);
3199 let use_local_tools_dir = self.use_local_tools_dir;
3200 let external_bin = opt_vec_lit(self.external_bin.as_ref(), str_lit);
3201 let windows = &self.windows;
3202 let license = opt_str_lit(self.license.as_ref());
3203 let license_file = opt_lit(self.license_file.as_ref().map(path_buf_lit).as_ref());
3204 let linux = quote!(Default::default());
3205 let macos = quote!(Default::default());
3206 let ios = quote!(Default::default());
3207 let android = quote!(Default::default());
3208
3209 literal_struct!(
3210 tokens,
3211 ::tauri::utils::config::BundleConfig,
3212 active,
3213 publisher,
3214 homepage,
3215 icon,
3216 targets,
3217 create_updater_artifacts,
3218 resources,
3219 copyright,
3220 category,
3221 license,
3222 license_file,
3223 file_associations,
3224 short_description,
3225 long_description,
3226 use_local_tools_dir,
3227 external_bin,
3228 windows,
3229 linux,
3230 macos,
3231 ios,
3232 android
3233 );
3234 }
3235 }
3236
3237 impl ToTokens for FrontendDist {
3238 fn to_tokens(&self, tokens: &mut TokenStream) {
3239 let prefix = quote! { ::tauri::utils::config::FrontendDist };
3240
3241 tokens.append_all(match self {
3242 Self::Url(url) => {
3243 let url = url_lit(url);
3244 quote! { #prefix::Url(#url) }
3245 }
3246 Self::Directory(path) => {
3247 let path = path_buf_lit(path);
3248 quote! { #prefix::Directory(#path) }
3249 }
3250 Self::Files(files) => {
3251 let files = vec_lit(files, path_buf_lit);
3252 quote! { #prefix::Files(#files) }
3253 }
3254 })
3255 }
3256 }
3257
3258 impl ToTokens for BuildConfig {
3259 fn to_tokens(&self, tokens: &mut TokenStream) {
3260 let dev_url = opt_lit(self.dev_url.as_ref().map(url_lit).as_ref());
3261 let frontend_dist = opt_lit(self.frontend_dist.as_ref());
3262 let runner = quote!(None);
3263 let before_dev_command = quote!(None);
3264 let before_build_command = quote!(None);
3265 let before_bundle_command = quote!(None);
3266 let features = quote!(None);
3267 let remove_unused_commands = quote!(false);
3268
3269 literal_struct!(
3270 tokens,
3271 ::tauri::utils::config::BuildConfig,
3272 runner,
3273 dev_url,
3274 frontend_dist,
3275 before_dev_command,
3276 before_build_command,
3277 before_bundle_command,
3278 features,
3279 remove_unused_commands
3280 );
3281 }
3282 }
3283
3284 impl ToTokens for CspDirectiveSources {
3285 fn to_tokens(&self, tokens: &mut TokenStream) {
3286 let prefix = quote! { ::tauri::utils::config::CspDirectiveSources };
3287
3288 tokens.append_all(match self {
3289 Self::Inline(sources) => {
3290 let sources = sources.as_str();
3291 quote!(#prefix::Inline(#sources.into()))
3292 }
3293 Self::List(list) => {
3294 let list = vec_lit(list, str_lit);
3295 quote!(#prefix::List(#list))
3296 }
3297 })
3298 }
3299 }
3300
3301 impl ToTokens for Csp {
3302 fn to_tokens(&self, tokens: &mut TokenStream) {
3303 let prefix = quote! { ::tauri::utils::config::Csp };
3304
3305 tokens.append_all(match self {
3306 Self::Policy(policy) => {
3307 let policy = policy.as_str();
3308 quote!(#prefix::Policy(#policy.into()))
3309 }
3310 Self::DirectiveMap(list) => {
3311 let map = map_lit(
3312 quote! { ::std::collections::HashMap },
3313 list,
3314 str_lit,
3315 identity,
3316 );
3317 quote!(#prefix::DirectiveMap(#map))
3318 }
3319 })
3320 }
3321 }
3322
3323 impl ToTokens for DisabledCspModificationKind {
3324 fn to_tokens(&self, tokens: &mut TokenStream) {
3325 let prefix = quote! { ::tauri::utils::config::DisabledCspModificationKind };
3326
3327 tokens.append_all(match self {
3328 Self::Flag(flag) => {
3329 quote! { #prefix::Flag(#flag) }
3330 }
3331 Self::List(directives) => {
3332 let directives = vec_lit(directives, str_lit);
3333 quote! { #prefix::List(#directives) }
3334 }
3335 });
3336 }
3337 }
3338
3339 impl ToTokens for CapabilityEntry {
3340 fn to_tokens(&self, tokens: &mut TokenStream) {
3341 let prefix = quote! { ::tauri::utils::config::CapabilityEntry };
3342
3343 tokens.append_all(match self {
3344 Self::Inlined(capability) => {
3345 quote! { #prefix::Inlined(#capability) }
3346 }
3347 Self::Reference(id) => {
3348 let id = str_lit(id);
3349 quote! { #prefix::Reference(#id) }
3350 }
3351 });
3352 }
3353 }
3354
3355 impl ToTokens for HeaderSource {
3356 fn to_tokens(&self, tokens: &mut TokenStream) {
3357 let prefix = quote! { ::tauri::utils::config::HeaderSource };
3358
3359 tokens.append_all(match self {
3360 Self::Inline(s) => {
3361 let line = s.as_str();
3362 quote!(#prefix::Inline(#line.into()))
3363 }
3364 Self::List(l) => {
3365 let list = vec_lit(l, str_lit);
3366 quote!(#prefix::List(#list))
3367 }
3368 Self::Map(m) => {
3369 let map = map_lit(quote! { ::std::collections::HashMap }, m, str_lit, str_lit);
3370 quote!(#prefix::Map(#map))
3371 }
3372 })
3373 }
3374 }
3375
3376 impl ToTokens for HeaderConfig {
3377 fn to_tokens(&self, tokens: &mut TokenStream) {
3378 let access_control_allow_credentials =
3379 opt_lit(self.access_control_allow_credentials.as_ref());
3380 let access_control_allow_headers = opt_lit(self.access_control_allow_headers.as_ref());
3381 let access_control_allow_methods = opt_lit(self.access_control_allow_methods.as_ref());
3382 let access_control_expose_headers = opt_lit(self.access_control_expose_headers.as_ref());
3383 let access_control_max_age = opt_lit(self.access_control_max_age.as_ref());
3384 let cross_origin_embedder_policy = opt_lit(self.cross_origin_embedder_policy.as_ref());
3385 let cross_origin_opener_policy = opt_lit(self.cross_origin_opener_policy.as_ref());
3386 let cross_origin_resource_policy = opt_lit(self.cross_origin_resource_policy.as_ref());
3387 let permissions_policy = opt_lit(self.permissions_policy.as_ref());
3388 let timing_allow_origin = opt_lit(self.timing_allow_origin.as_ref());
3389 let x_content_type_options = opt_lit(self.x_content_type_options.as_ref());
3390 let tauri_custom_header = opt_lit(self.tauri_custom_header.as_ref());
3391
3392 literal_struct!(
3393 tokens,
3394 ::tauri::utils::config::HeaderConfig,
3395 access_control_allow_credentials,
3396 access_control_allow_headers,
3397 access_control_allow_methods,
3398 access_control_expose_headers,
3399 access_control_max_age,
3400 cross_origin_embedder_policy,
3401 cross_origin_opener_policy,
3402 cross_origin_resource_policy,
3403 permissions_policy,
3404 timing_allow_origin,
3405 x_content_type_options,
3406 tauri_custom_header
3407 );
3408 }
3409 }
3410
3411 impl ToTokens for SecurityConfig {
3412 fn to_tokens(&self, tokens: &mut TokenStream) {
3413 let csp = opt_lit(self.csp.as_ref());
3414 let dev_csp = opt_lit(self.dev_csp.as_ref());
3415 let freeze_prototype = self.freeze_prototype;
3416 let dangerous_disable_asset_csp_modification = &self.dangerous_disable_asset_csp_modification;
3417 let asset_protocol = &self.asset_protocol;
3418 let pattern = &self.pattern;
3419 let capabilities = vec_lit(&self.capabilities, identity);
3420 let headers = opt_lit(self.headers.as_ref());
3421
3422 literal_struct!(
3423 tokens,
3424 ::tauri::utils::config::SecurityConfig,
3425 csp,
3426 dev_csp,
3427 freeze_prototype,
3428 dangerous_disable_asset_csp_modification,
3429 asset_protocol,
3430 pattern,
3431 capabilities,
3432 headers
3433 );
3434 }
3435 }
3436
3437 impl ToTokens for TrayIconConfig {
3438 fn to_tokens(&self, tokens: &mut TokenStream) {
3439 let id = opt_str_lit(self.id.as_ref());
3440 let icon_as_template = self.icon_as_template;
3441 #[allow(deprecated)]
3442 let menu_on_left_click = self.menu_on_left_click;
3443 let show_menu_on_left_click = self.show_menu_on_left_click;
3444 let icon_path = path_buf_lit(&self.icon_path);
3445 let title = opt_str_lit(self.title.as_ref());
3446 let tooltip = opt_str_lit(self.tooltip.as_ref());
3447 literal_struct!(
3448 tokens,
3449 ::tauri::utils::config::TrayIconConfig,
3450 id,
3451 icon_path,
3452 icon_as_template,
3453 menu_on_left_click,
3454 show_menu_on_left_click,
3455 title,
3456 tooltip
3457 );
3458 }
3459 }
3460
3461 impl ToTokens for FsScope {
3462 fn to_tokens(&self, tokens: &mut TokenStream) {
3463 let prefix = quote! { ::tauri::utils::config::FsScope };
3464
3465 tokens.append_all(match self {
3466 Self::AllowedPaths(allow) => {
3467 let allowed_paths = vec_lit(allow, path_buf_lit);
3468 quote! { #prefix::AllowedPaths(#allowed_paths) }
3469 }
3470 Self::Scope { allow, deny , require_literal_leading_dot} => {
3471 let allow = vec_lit(allow, path_buf_lit);
3472 let deny = vec_lit(deny, path_buf_lit);
3473 let require_literal_leading_dot = opt_lit(require_literal_leading_dot.as_ref());
3474 quote! { #prefix::Scope { allow: #allow, deny: #deny, require_literal_leading_dot: #require_literal_leading_dot } }
3475 }
3476 });
3477 }
3478 }
3479
3480 impl ToTokens for AssetProtocolConfig {
3481 fn to_tokens(&self, tokens: &mut TokenStream) {
3482 let scope = &self.scope;
3483 tokens.append_all(quote! { ::tauri::utils::config::AssetProtocolConfig { scope: #scope, ..Default::default() } })
3484 }
3485 }
3486
3487 impl ToTokens for AppConfig {
3488 fn to_tokens(&self, tokens: &mut TokenStream) {
3489 let windows = vec_lit(&self.windows, identity);
3490 let security = &self.security;
3491 let tray_icon = opt_lit(self.tray_icon.as_ref());
3492 let macos_private_api = self.macos_private_api;
3493 let with_global_tauri = self.with_global_tauri;
3494 let enable_gtk_app_id = self.enable_gtk_app_id;
3495
3496 literal_struct!(
3497 tokens,
3498 ::tauri::utils::config::AppConfig,
3499 windows,
3500 security,
3501 tray_icon,
3502 macos_private_api,
3503 with_global_tauri,
3504 enable_gtk_app_id
3505 );
3506 }
3507 }
3508
3509 impl ToTokens for PluginConfig {
3510 fn to_tokens(&self, tokens: &mut TokenStream) {
3511 let config = map_lit(
3512 quote! { ::std::collections::HashMap },
3513 &self.0,
3514 str_lit,
3515 json_value_lit,
3516 );
3517 tokens.append_all(quote! { ::tauri::utils::config::PluginConfig(#config) })
3518 }
3519 }
3520
3521 impl ToTokens for Config {
3522 fn to_tokens(&self, tokens: &mut TokenStream) {
3523 let schema = quote!(None);
3524 let product_name = opt_str_lit(self.product_name.as_ref());
3525 let main_binary_name = opt_str_lit(self.main_binary_name.as_ref());
3526 let version = opt_str_lit(self.version.as_ref());
3527 let identifier = str_lit(&self.identifier);
3528 let app = &self.app;
3529 let build = &self.build;
3530 let bundle = &self.bundle;
3531 let plugins = &self.plugins;
3532
3533 literal_struct!(
3534 tokens,
3535 ::tauri::utils::config::Config,
3536 schema,
3537 product_name,
3538 main_binary_name,
3539 version,
3540 identifier,
3541 app,
3542 build,
3543 bundle,
3544 plugins
3545 );
3546 }
3547 }
3548}
3549
3550#[cfg(test)]
3551mod test {
3552 use super::*;
3553
3554 #[test]
3557 fn test_defaults() {
3559 let a_config = AppConfig::default();
3561 let b_config = BuildConfig::default();
3563 let d_windows: Vec<WindowConfig> = vec![];
3565 let d_bundle = BundleConfig::default();
3567
3568 let app = AppConfig {
3570 windows: vec![],
3571 security: SecurityConfig {
3572 csp: None,
3573 dev_csp: None,
3574 freeze_prototype: false,
3575 dangerous_disable_asset_csp_modification: DisabledCspModificationKind::Flag(false),
3576 asset_protocol: AssetProtocolConfig::default(),
3577 pattern: Default::default(),
3578 capabilities: Vec::new(),
3579 headers: None,
3580 },
3581 tray_icon: None,
3582 macos_private_api: false,
3583 with_global_tauri: false,
3584 enable_gtk_app_id: false,
3585 };
3586
3587 let build = BuildConfig {
3589 runner: None,
3590 dev_url: None,
3591 frontend_dist: None,
3592 before_dev_command: None,
3593 before_build_command: None,
3594 before_bundle_command: None,
3595 features: None,
3596 remove_unused_commands: false,
3597 };
3598
3599 let bundle = BundleConfig {
3601 active: false,
3602 targets: Default::default(),
3603 create_updater_artifacts: Default::default(),
3604 publisher: None,
3605 homepage: None,
3606 icon: Vec::new(),
3607 resources: None,
3608 copyright: None,
3609 category: None,
3610 file_associations: None,
3611 short_description: None,
3612 long_description: None,
3613 use_local_tools_dir: false,
3614 license: None,
3615 license_file: None,
3616 linux: Default::default(),
3617 macos: Default::default(),
3618 external_bin: None,
3619 windows: Default::default(),
3620 ios: Default::default(),
3621 android: Default::default(),
3622 };
3623
3624 assert_eq!(a_config, app);
3626 assert_eq!(b_config, build);
3627 assert_eq!(d_bundle, bundle);
3628 assert_eq!(d_windows, app.windows);
3629 }
3630
3631 #[test]
3632 fn parse_hex_color() {
3633 use super::Color;
3634
3635 assert_eq!(Color(255, 255, 255, 255), "fff".parse().unwrap());
3636 assert_eq!(Color(255, 255, 255, 255), "#fff".parse().unwrap());
3637 assert_eq!(Color(0, 0, 0, 255), "#000000".parse().unwrap());
3638 assert_eq!(Color(0, 0, 0, 255), "#000000ff".parse().unwrap());
3639 assert_eq!(Color(0, 255, 0, 255), "#00ff00ff".parse().unwrap());
3640 }
3641}