1use http::response::Builder;
27#[cfg(feature = "schema")]
28use schemars::schema::Schema;
29#[cfg(feature = "schema")]
30use schemars::JsonSchema;
31use semver::Version;
32use serde::{
33 de::{Deserializer, Error as DeError, Visitor},
34 Deserialize, Serialize, Serializer,
35};
36use serde_json::Value as JsonValue;
37use serde_untagged::UntaggedEnumVisitor;
38use serde_with::skip_serializing_none;
39use url::Url;
40
41use std::{
42 collections::{HashMap, HashSet},
43 fmt::{self, Display},
44 fs::read_to_string,
45 path::PathBuf,
46 str::FromStr,
47};
48
49#[cfg(feature = "schema")]
50fn add_description(schema: Schema, description: impl Into<String>) -> Schema {
51 let value = description.into();
52 if value.is_empty() {
53 schema
54 } else {
55 let mut schema_obj = schema.into_object();
56 schema_obj.metadata().description = value.into();
57 Schema::Object(schema_obj)
58 }
59}
60
61pub mod parse;
63
64use crate::{acl::capability::Capability, TitleBarStyle, WindowEffect, WindowEffectState};
65
66pub use self::parse::parse;
67
68fn default_true() -> bool {
69 true
70}
71
72#[derive(PartialEq, Eq, Debug, Clone, Serialize)]
74#[cfg_attr(feature = "schema", derive(JsonSchema))]
75#[serde(untagged)]
76#[non_exhaustive]
77pub enum WebviewUrl {
78 External(Url),
80 App(PathBuf),
84 CustomProtocol(Url),
86}
87
88impl<'de> Deserialize<'de> for WebviewUrl {
89 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
90 where
91 D: Deserializer<'de>,
92 {
93 #[derive(Deserialize)]
94 #[serde(untagged)]
95 enum WebviewUrlDeserializer {
96 Url(Url),
97 Path(PathBuf),
98 }
99
100 match WebviewUrlDeserializer::deserialize(deserializer)? {
101 WebviewUrlDeserializer::Url(u) => {
102 if u.scheme() == "https" || u.scheme() == "http" {
103 Ok(Self::External(u))
104 } else {
105 Ok(Self::CustomProtocol(u))
106 }
107 }
108 WebviewUrlDeserializer::Path(p) => Ok(Self::App(p)),
109 }
110 }
111}
112
113impl fmt::Display for WebviewUrl {
114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115 match self {
116 Self::External(url) | Self::CustomProtocol(url) => write!(f, "{url}"),
117 Self::App(path) => write!(f, "{}", path.display()),
118 }
119 }
120}
121
122impl Default for WebviewUrl {
123 fn default() -> Self {
124 Self::App("index.html".into())
125 }
126}
127
128#[derive(Debug, PartialEq, Eq, Clone)]
130#[cfg_attr(feature = "schema", derive(JsonSchema))]
131#[cfg_attr(feature = "schema", schemars(rename_all = "lowercase"))]
132pub enum BundleType {
133 Deb,
135 Rpm,
137 AppImage,
139 Msi,
141 Nsis,
143 App,
145 Dmg,
147}
148
149impl BundleType {
150 fn all() -> &'static [Self] {
152 &[
153 BundleType::Deb,
154 BundleType::Rpm,
155 BundleType::AppImage,
156 BundleType::Msi,
157 BundleType::Nsis,
158 BundleType::App,
159 BundleType::Dmg,
160 ]
161 }
162}
163
164impl Display for BundleType {
165 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166 write!(
167 f,
168 "{}",
169 match self {
170 Self::Deb => "deb",
171 Self::Rpm => "rpm",
172 Self::AppImage => "appimage",
173 Self::Msi => "msi",
174 Self::Nsis => "nsis",
175 Self::App => "app",
176 Self::Dmg => "dmg",
177 }
178 )
179 }
180}
181
182impl Serialize for BundleType {
183 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
184 where
185 S: Serializer,
186 {
187 serializer.serialize_str(self.to_string().as_ref())
188 }
189}
190
191impl<'de> Deserialize<'de> for BundleType {
192 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
193 where
194 D: Deserializer<'de>,
195 {
196 let s = String::deserialize(deserializer)?;
197 match s.to_lowercase().as_str() {
198 "deb" => Ok(Self::Deb),
199 "rpm" => Ok(Self::Rpm),
200 "appimage" => Ok(Self::AppImage),
201 "msi" => Ok(Self::Msi),
202 "nsis" => Ok(Self::Nsis),
203 "app" => Ok(Self::App),
204 "dmg" => Ok(Self::Dmg),
205 _ => Err(DeError::custom(format!("unknown bundle target '{s}'"))),
206 }
207 }
208}
209
210#[derive(Debug, PartialEq, Eq, Clone, Default)]
212pub enum BundleTarget {
213 #[default]
215 All,
216 List(Vec<BundleType>),
218 One(BundleType),
220}
221
222#[cfg(feature = "schema")]
223impl schemars::JsonSchema for BundleTarget {
224 fn schema_name() -> std::string::String {
225 "BundleTarget".to_owned()
226 }
227
228 fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
229 let any_of = vec![
230 schemars::schema::SchemaObject {
231 const_value: Some("all".into()),
232 metadata: Some(Box::new(schemars::schema::Metadata {
233 description: Some("Bundle all targets.".to_owned()),
234 ..Default::default()
235 })),
236 ..Default::default()
237 }
238 .into(),
239 add_description(
240 gen.subschema_for::<Vec<BundleType>>(),
241 "A list of bundle targets.",
242 ),
243 add_description(gen.subschema_for::<BundleType>(), "A single bundle target."),
244 ];
245
246 schemars::schema::SchemaObject {
247 subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
248 any_of: Some(any_of),
249 ..Default::default()
250 })),
251 metadata: Some(Box::new(schemars::schema::Metadata {
252 description: Some("Targets to bundle. Each value is case insensitive.".to_owned()),
253 ..Default::default()
254 })),
255 ..Default::default()
256 }
257 .into()
258 }
259}
260
261impl Serialize for BundleTarget {
262 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
263 where
264 S: Serializer,
265 {
266 match self {
267 Self::All => serializer.serialize_str("all"),
268 Self::List(l) => l.serialize(serializer),
269 Self::One(t) => serializer.serialize_str(t.to_string().as_ref()),
270 }
271 }
272}
273
274impl<'de> Deserialize<'de> for BundleTarget {
275 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
276 where
277 D: Deserializer<'de>,
278 {
279 #[derive(Deserialize, Serialize)]
280 #[serde(untagged)]
281 pub enum BundleTargetInner {
282 List(Vec<BundleType>),
283 One(BundleType),
284 All(String),
285 }
286
287 match BundleTargetInner::deserialize(deserializer)? {
288 BundleTargetInner::All(s) if s.to_lowercase() == "all" => Ok(Self::All),
289 BundleTargetInner::All(t) => Err(DeError::custom(format!(
290 "invalid bundle type {t}, expected one of `all`, {}",
291 BundleType::all()
292 .iter()
293 .map(|b| format!("`{b}`"))
294 .collect::<Vec<_>>()
295 .join(", ")
296 ))),
297 BundleTargetInner::List(l) => Ok(Self::List(l)),
298 BundleTargetInner::One(t) => Ok(Self::One(t)),
299 }
300 }
301}
302
303impl BundleTarget {
304 #[allow(dead_code)]
306 pub fn to_vec(&self) -> Vec<BundleType> {
307 match self {
308 Self::All => BundleType::all().to_vec(),
309 Self::List(list) => list.clone(),
310 Self::One(i) => vec![i.clone()],
311 }
312 }
313}
314
315#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
319#[cfg_attr(feature = "schema", derive(JsonSchema))]
320#[serde(rename_all = "camelCase", deny_unknown_fields)]
321pub struct AppImageConfig {
322 #[serde(default, alias = "bundle-media-framework")]
325 pub bundle_media_framework: bool,
326 #[serde(default)]
328 pub files: HashMap<PathBuf, PathBuf>,
329}
330
331#[skip_serializing_none]
335#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
336#[cfg_attr(feature = "schema", derive(JsonSchema))]
337#[serde(rename_all = "camelCase", deny_unknown_fields)]
338pub struct DebConfig {
339 pub depends: Option<Vec<String>>,
341 pub recommends: Option<Vec<String>>,
343 pub provides: Option<Vec<String>>,
345 pub conflicts: Option<Vec<String>>,
347 pub replaces: Option<Vec<String>>,
349 #[serde(default)]
351 pub files: HashMap<PathBuf, PathBuf>,
352 pub section: Option<String>,
354 pub priority: Option<String>,
357 pub changelog: Option<PathBuf>,
360 #[serde(alias = "desktop-template")]
364 pub desktop_template: Option<PathBuf>,
365 #[serde(alias = "pre-install-script")]
368 pub pre_install_script: Option<PathBuf>,
369 #[serde(alias = "post-install-script")]
372 pub post_install_script: Option<PathBuf>,
373 #[serde(alias = "pre-remove-script")]
376 pub pre_remove_script: Option<PathBuf>,
377 #[serde(alias = "post-remove-script")]
380 pub post_remove_script: Option<PathBuf>,
381}
382
383#[skip_serializing_none]
387#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
388#[cfg_attr(feature = "schema", derive(JsonSchema))]
389#[serde(rename_all = "camelCase", deny_unknown_fields)]
390pub struct LinuxConfig {
391 #[serde(default)]
393 pub appimage: AppImageConfig,
394 #[serde(default)]
396 pub deb: DebConfig,
397 #[serde(default)]
399 pub rpm: RpmConfig,
400}
401
402#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
404#[cfg_attr(feature = "schema", derive(JsonSchema))]
405#[serde(rename_all = "camelCase", deny_unknown_fields, tag = "type")]
406#[non_exhaustive]
407pub enum RpmCompression {
408 Gzip {
410 level: u32,
412 },
413 Zstd {
415 level: i32,
417 },
418 Xz {
420 level: u32,
422 },
423 Bzip2 {
425 level: u32,
427 },
428 None,
430}
431
432#[skip_serializing_none]
434#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
435#[cfg_attr(feature = "schema", derive(JsonSchema))]
436#[serde(rename_all = "camelCase", deny_unknown_fields)]
437pub struct RpmConfig {
438 pub depends: Option<Vec<String>>,
440 pub recommends: Option<Vec<String>>,
442 pub provides: Option<Vec<String>>,
444 pub conflicts: Option<Vec<String>>,
447 pub obsoletes: Option<Vec<String>>,
450 #[serde(default = "default_release")]
452 pub release: String,
453 #[serde(default)]
455 pub epoch: u32,
456 #[serde(default)]
458 pub files: HashMap<PathBuf, PathBuf>,
459 #[serde(alias = "desktop-template")]
463 pub desktop_template: Option<PathBuf>,
464 #[serde(alias = "pre-install-script")]
467 pub pre_install_script: Option<PathBuf>,
468 #[serde(alias = "post-install-script")]
471 pub post_install_script: Option<PathBuf>,
472 #[serde(alias = "pre-remove-script")]
475 pub pre_remove_script: Option<PathBuf>,
476 #[serde(alias = "post-remove-script")]
479 pub post_remove_script: Option<PathBuf>,
480 pub compression: Option<RpmCompression>,
482}
483
484impl Default for RpmConfig {
485 fn default() -> Self {
486 Self {
487 depends: None,
488 recommends: None,
489 provides: None,
490 conflicts: None,
491 obsoletes: None,
492 release: default_release(),
493 epoch: 0,
494 files: Default::default(),
495 desktop_template: None,
496 pre_install_script: None,
497 post_install_script: None,
498 pre_remove_script: None,
499 post_remove_script: None,
500 compression: None,
501 }
502 }
503}
504
505fn default_release() -> String {
506 "1".into()
507}
508
509#[derive(Default, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
511#[cfg_attr(feature = "schema", derive(JsonSchema))]
512#[serde(rename_all = "camelCase", deny_unknown_fields)]
513pub struct Position {
514 pub x: u32,
516 pub y: u32,
518}
519
520#[derive(Default, Debug, PartialEq, Clone, Deserialize, Serialize)]
522#[cfg_attr(feature = "schema", derive(JsonSchema))]
523#[serde(rename_all = "camelCase", deny_unknown_fields)]
524pub struct LogicalPosition {
525 pub x: f64,
527 pub y: f64,
529}
530
531#[derive(Default, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
533#[cfg_attr(feature = "schema", derive(JsonSchema))]
534#[serde(rename_all = "camelCase", deny_unknown_fields)]
535pub struct Size {
536 pub width: u32,
538 pub height: u32,
540}
541
542#[skip_serializing_none]
546#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
547#[cfg_attr(feature = "schema", derive(JsonSchema))]
548#[serde(rename_all = "camelCase", deny_unknown_fields)]
549pub struct DmgConfig {
550 pub background: Option<PathBuf>,
552 pub window_position: Option<Position>,
554 #[serde(default = "dmg_window_size", alias = "window-size")]
556 pub window_size: Size,
557 #[serde(default = "dmg_app_position", alias = "app-position")]
559 pub app_position: Position,
560 #[serde(
562 default = "dmg_application_folder_position",
563 alias = "application-folder-position"
564 )]
565 pub application_folder_position: Position,
566}
567
568impl Default for DmgConfig {
569 fn default() -> Self {
570 Self {
571 background: None,
572 window_position: None,
573 window_size: dmg_window_size(),
574 app_position: dmg_app_position(),
575 application_folder_position: dmg_application_folder_position(),
576 }
577 }
578}
579
580fn dmg_window_size() -> Size {
581 Size {
582 width: 660,
583 height: 400,
584 }
585}
586
587fn dmg_app_position() -> Position {
588 Position { x: 180, y: 170 }
589}
590
591fn dmg_application_folder_position() -> Position {
592 Position { x: 480, y: 170 }
593}
594
595fn de_macos_minimum_system_version<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
596where
597 D: Deserializer<'de>,
598{
599 let version = Option::<String>::deserialize(deserializer)?;
600 match version {
601 Some(v) if v.is_empty() => Ok(macos_minimum_system_version()),
602 e => Ok(e),
603 }
604}
605
606#[skip_serializing_none]
610#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
611#[cfg_attr(feature = "schema", derive(JsonSchema))]
612#[serde(rename_all = "camelCase", deny_unknown_fields)]
613pub struct MacConfig {
614 pub frameworks: Option<Vec<String>>,
618 #[serde(default)]
620 pub files: HashMap<PathBuf, PathBuf>,
621 #[serde(alias = "bundle-version")]
625 pub bundle_version: Option<String>,
626 #[serde(alias = "bundle-name")]
632 pub bundle_name: Option<String>,
633 #[serde(
642 deserialize_with = "de_macos_minimum_system_version",
643 default = "macos_minimum_system_version",
644 alias = "minimum-system-version"
645 )]
646 pub minimum_system_version: Option<String>,
647 #[serde(alias = "exception-domain")]
650 pub exception_domain: Option<String>,
651 #[serde(alias = "signing-identity")]
653 pub signing_identity: Option<String>,
654 #[serde(alias = "hardened-runtime", default = "default_true")]
656 pub hardened_runtime: bool,
657 #[serde(alias = "provider-short-name")]
659 pub provider_short_name: Option<String>,
660 pub entitlements: Option<String>,
662 #[serde(alias = "info-plist")]
666 pub info_plist: Option<PathBuf>,
667 #[serde(default)]
669 pub dmg: DmgConfig,
670}
671
672impl Default for MacConfig {
673 fn default() -> Self {
674 Self {
675 frameworks: None,
676 files: HashMap::new(),
677 bundle_version: None,
678 bundle_name: None,
679 minimum_system_version: macos_minimum_system_version(),
680 exception_domain: None,
681 signing_identity: None,
682 hardened_runtime: true,
683 provider_short_name: None,
684 entitlements: None,
685 info_plist: None,
686 dmg: Default::default(),
687 }
688 }
689}
690
691fn macos_minimum_system_version() -> Option<String> {
692 Some("10.13".into())
693}
694
695fn ios_minimum_system_version() -> String {
696 "14.0".into()
697}
698
699#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
703#[cfg_attr(feature = "schema", derive(JsonSchema))]
704#[serde(rename_all = "camelCase", deny_unknown_fields)]
705pub struct WixLanguageConfig {
706 #[serde(alias = "locale-path")]
708 pub locale_path: Option<String>,
709}
710
711#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
713#[cfg_attr(feature = "schema", derive(JsonSchema))]
714#[serde(untagged)]
715pub enum WixLanguage {
716 One(String),
718 List(Vec<String>),
720 Localized(HashMap<String, WixLanguageConfig>),
722}
723
724impl Default for WixLanguage {
725 fn default() -> Self {
726 Self::One("en-US".into())
727 }
728}
729
730#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
734#[cfg_attr(feature = "schema", derive(JsonSchema))]
735#[serde(rename_all = "camelCase", deny_unknown_fields)]
736pub struct WixConfig {
737 pub version: Option<String>,
746 #[serde(alias = "upgrade-code")]
755 pub upgrade_code: Option<uuid::Uuid>,
756 #[serde(default)]
758 pub language: WixLanguage,
759 pub template: Option<PathBuf>,
761 #[serde(default, alias = "fragment-paths")]
763 pub fragment_paths: Vec<PathBuf>,
764 #[serde(default, alias = "component-group-refs")]
766 pub component_group_refs: Vec<String>,
767 #[serde(default, alias = "component-refs")]
769 pub component_refs: Vec<String>,
770 #[serde(default, alias = "feature-group-refs")]
772 pub feature_group_refs: Vec<String>,
773 #[serde(default, alias = "feature-refs")]
775 pub feature_refs: Vec<String>,
776 #[serde(default, alias = "merge-refs")]
778 pub merge_refs: Vec<String>,
779 #[serde(default, alias = "enable-elevated-update-task")]
781 pub enable_elevated_update_task: bool,
782 #[serde(alias = "banner-path")]
787 pub banner_path: Option<PathBuf>,
788 #[serde(alias = "dialog-image-path")]
793 pub dialog_image_path: Option<PathBuf>,
794 #[serde(default, alias = "fips-compliant")]
797 pub fips_compliant: bool,
798}
799
800#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Default)]
804#[cfg_attr(feature = "schema", derive(JsonSchema))]
805#[serde(rename_all = "camelCase", deny_unknown_fields)]
806pub enum NsisCompression {
807 Zlib,
809 Bzip2,
811 #[default]
813 Lzma,
814 None,
816}
817
818#[derive(Default, Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
820#[serde(rename_all = "camelCase", deny_unknown_fields)]
821#[cfg_attr(feature = "schema", derive(JsonSchema))]
822pub enum NSISInstallerMode {
823 #[default]
829 CurrentUser,
830 PerMachine,
835 Both,
841}
842
843#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
845#[cfg_attr(feature = "schema", derive(JsonSchema))]
846#[serde(rename_all = "camelCase", deny_unknown_fields)]
847pub struct NsisConfig {
848 pub template: Option<PathBuf>,
850 #[serde(alias = "header-image")]
854 pub header_image: Option<PathBuf>,
855 #[serde(alias = "sidebar-image")]
859 pub sidebar_image: Option<PathBuf>,
860 #[serde(alias = "install-icon")]
863 pub installer_icon: Option<PathBuf>,
864 #[serde(alias = "uninstaller-icon")]
866 pub uninstaller_icon: Option<PathBuf>,
867 #[serde(alias = "uninstaller-header-image")]
872 pub uninstaller_header_image: Option<PathBuf>,
873 #[serde(default, alias = "install-mode")]
875 pub install_mode: NSISInstallerMode,
876 pub languages: Option<Vec<String>>,
883 pub custom_language_files: Option<HashMap<String, PathBuf>>,
890 #[serde(default, alias = "display-language-selector")]
893 pub display_language_selector: bool,
894 #[serde(default)]
898 pub compression: NsisCompression,
899 #[serde(alias = "start-menu-folder")]
908 pub start_menu_folder: Option<String>,
909 #[serde(alias = "installer-hooks")]
939 pub installer_hooks: Option<PathBuf>,
940 #[deprecated(
946 since = "2.10.0",
947 note = "Use `WindowsConfig::minimum_webview2_version` instead."
948 )]
949 #[serde(alias = "minimum-webview2-version")]
950 pub minimum_webview2_version: Option<String>,
951}
952
953#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
958#[serde(tag = "type", rename_all = "camelCase", deny_unknown_fields)]
959#[cfg_attr(feature = "schema", derive(JsonSchema))]
960pub enum WebviewInstallMode {
961 Skip,
963 DownloadBootstrapper {
967 #[serde(default = "default_true")]
969 silent: bool,
970 },
971 EmbedBootstrapper {
975 #[serde(default = "default_true")]
977 silent: bool,
978 },
979 OfflineInstaller {
983 #[serde(default = "default_true")]
985 silent: bool,
986 },
987 FixedRuntime {
990 path: PathBuf,
995 },
996}
997
998impl Default for WebviewInstallMode {
999 fn default() -> Self {
1000 Self::DownloadBootstrapper { silent: true }
1001 }
1002}
1003
1004#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1006#[cfg_attr(feature = "schema", derive(JsonSchema))]
1007#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
1008pub enum CustomSignCommandConfig {
1009 Command(String),
1018 CommandWithOptions {
1023 cmd: String,
1025 args: Vec<String>,
1029 },
1030}
1031
1032#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1036#[cfg_attr(feature = "schema", derive(JsonSchema))]
1037#[serde(rename_all = "camelCase", deny_unknown_fields)]
1038pub struct WindowsConfig {
1039 #[serde(alias = "digest-algorithm")]
1042 pub digest_algorithm: Option<String>,
1043 #[serde(alias = "certificate-thumbprint")]
1045 pub certificate_thumbprint: Option<String>,
1046 #[serde(alias = "timestamp-url")]
1048 pub timestamp_url: Option<String>,
1049 #[serde(default)]
1052 pub tsp: bool,
1053 #[serde(default, alias = "webview-install-mode")]
1055 pub webview_install_mode: WebviewInstallMode,
1056 #[serde(default = "default_true", alias = "allow-downgrades")]
1062 pub allow_downgrades: bool,
1063 #[serde(alias = "minimum-webview2-version")]
1067 pub minimum_webview2_version: Option<String>,
1068 pub wix: Option<WixConfig>,
1070 pub nsis: Option<NsisConfig>,
1072 #[serde(alias = "sign-command")]
1080 pub sign_command: Option<CustomSignCommandConfig>,
1081}
1082
1083impl Default for WindowsConfig {
1084 fn default() -> Self {
1085 Self {
1086 digest_algorithm: None,
1087 certificate_thumbprint: None,
1088 timestamp_url: None,
1089 tsp: false,
1090 webview_install_mode: Default::default(),
1091 allow_downgrades: true,
1092 minimum_webview2_version: None,
1093 wix: None,
1094 nsis: None,
1095 sign_command: None,
1096 }
1097 }
1098}
1099
1100#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1102#[cfg_attr(feature = "schema", derive(JsonSchema))]
1103pub enum BundleTypeRole {
1104 #[default]
1106 Editor,
1107 Viewer,
1109 Shell,
1111 QLGenerator,
1113 None,
1115}
1116
1117impl Display for BundleTypeRole {
1118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1119 match self {
1120 Self::Editor => write!(f, "Editor"),
1121 Self::Viewer => write!(f, "Viewer"),
1122 Self::Shell => write!(f, "Shell"),
1123 Self::QLGenerator => write!(f, "QLGenerator"),
1124 Self::None => write!(f, "None"),
1125 }
1126 }
1127}
1128
1129#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1133#[cfg_attr(feature = "schema", derive(JsonSchema))]
1134pub enum HandlerRank {
1135 #[default]
1137 Default,
1138 Owner,
1140 Alternate,
1142 None,
1144}
1145
1146impl Display for HandlerRank {
1147 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1148 match self {
1149 Self::Default => write!(f, "Default"),
1150 Self::Owner => write!(f, "Owner"),
1151 Self::Alternate => write!(f, "Alternate"),
1152 Self::None => write!(f, "None"),
1153 }
1154 }
1155}
1156
1157#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
1161#[cfg_attr(feature = "schema", derive(JsonSchema))]
1162pub struct AssociationExt(pub String);
1163
1164impl fmt::Display for AssociationExt {
1165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1166 write!(f, "{}", self.0)
1167 }
1168}
1169
1170impl<'d> serde::Deserialize<'d> for AssociationExt {
1171 fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {
1172 let ext = String::deserialize(deserializer)?;
1173 if let Some(ext) = ext.strip_prefix('.') {
1174 Ok(AssociationExt(ext.into()))
1175 } else {
1176 Ok(AssociationExt(ext))
1177 }
1178 }
1179}
1180
1181#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1183#[cfg_attr(feature = "schema", derive(JsonSchema))]
1184#[serde(rename_all = "camelCase", deny_unknown_fields)]
1185pub struct FileAssociation {
1186 pub ext: Vec<AssociationExt>,
1188 #[serde(alias = "content-types")]
1193 pub content_types: Option<Vec<String>>,
1194 pub name: Option<String>,
1196 pub description: Option<String>,
1198 #[serde(default)]
1200 pub role: BundleTypeRole,
1201 #[serde(alias = "mime-type")]
1209 pub mime_type: Option<String>,
1210 #[serde(default)]
1212 pub rank: HandlerRank,
1213 pub exported_type: Option<ExportedFileAssociation>,
1217 #[serde(alias = "android-intent-action-filters")]
1221 pub android_intent_action_filters: Option<Vec<AndroidIntentAction>>,
1222}
1223
1224#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Hash)]
1226#[cfg_attr(feature = "schema", derive(JsonSchema))]
1227#[serde(rename_all = "camelCase")]
1228#[non_exhaustive]
1229pub enum AndroidIntentAction {
1230 Send,
1234 SendMultiple,
1238 View,
1242}
1243
1244#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1246#[cfg_attr(feature = "schema", derive(JsonSchema))]
1247#[serde(rename_all = "camelCase", deny_unknown_fields)]
1248pub struct ExportedFileAssociation {
1249 pub identifier: String,
1251 #[serde(alias = "conforms-to")]
1255 pub conforms_to: Option<Vec<String>>,
1256}
1257
1258impl FileAssociation {
1259 pub fn infer_content_types(&self) -> HashSet<String> {
1266 let mut content_types = HashSet::new();
1267
1268 if let Some(exported_type) = &self.exported_type {
1270 content_types.insert(exported_type.identifier.clone());
1271 return content_types;
1272 }
1273
1274 if let Some(explicit_types) = &self.content_types {
1276 content_types.extend(explicit_types.iter().cloned());
1277 }
1278
1279 for ext in &self.ext {
1281 if let Some(uti) = extension_to_uti(&ext.0) {
1282 content_types.insert(uti.to_string());
1283 }
1284 }
1285
1286 if let Some(mime_type) = &self.mime_type {
1288 if let Some(uti) = mime_type_to_uti(mime_type) {
1289 content_types.insert(uti.to_string());
1290 }
1291 }
1292
1293 content_types
1294 }
1295}
1296
1297pub fn file_associations_plist(associations: &[FileAssociation]) -> Option<plist::Value> {
1303 use plist::{Dictionary, Value};
1304
1305 if associations.is_empty() {
1306 return None;
1307 }
1308
1309 let exported_associations = associations
1310 .iter()
1311 .filter_map(|association| {
1312 association.exported_type.as_ref().map(|exported_type| {
1313 let mut dict = Dictionary::new();
1314
1315 dict.insert(
1316 "UTTypeIdentifier".into(),
1317 exported_type.identifier.clone().into(),
1318 );
1319 if let Some(description) = &association.description {
1320 dict.insert("UTTypeDescription".into(), description.clone().into());
1321 }
1322 if let Some(conforms_to) = &exported_type.conforms_to {
1323 dict.insert(
1324 "UTTypeConformsTo".into(),
1325 Value::Array(conforms_to.iter().map(|s| s.clone().into()).collect()),
1326 );
1327 }
1328
1329 let mut specification = Dictionary::new();
1330 specification.insert(
1331 "public.filename-extension".into(),
1332 Value::Array(
1333 association
1334 .ext
1335 .iter()
1336 .map(|s| s.to_string().into())
1337 .collect(),
1338 ),
1339 );
1340 if let Some(mime_type) = &association.mime_type {
1341 specification.insert("public.mime-type".into(), mime_type.clone().into());
1342 }
1343
1344 dict.insert("UTTypeTagSpecification".into(), specification.into());
1345
1346 Value::Dictionary(dict)
1347 })
1348 })
1349 .collect::<Vec<_>>();
1350
1351 let document_types = associations
1352 .iter()
1353 .map(|association| {
1354 let mut dict = Dictionary::new();
1355
1356 if !association.ext.is_empty() {
1357 dict.insert(
1358 "CFBundleTypeExtensions".into(),
1359 Value::Array(
1360 association
1361 .ext
1362 .iter()
1363 .map(|ext| ext.to_string().into())
1364 .collect(),
1365 ),
1366 );
1367 }
1368
1369 let content_types = association.infer_content_types();
1371
1372 if !content_types.is_empty() {
1374 dict.insert(
1375 "LSItemContentTypes".into(),
1376 Value::Array(content_types.iter().map(|s| s.clone().into()).collect()),
1377 );
1378 }
1379
1380 let type_name = association
1381 .name
1382 .clone()
1383 .or_else(|| association.ext.first().map(|ext| ext.0.clone()))
1384 .unwrap_or_default();
1385 dict.insert("CFBundleTypeName".into(), type_name.into());
1386 dict.insert(
1387 "CFBundleTypeRole".into(),
1388 association.role.to_string().into(),
1389 );
1390 dict.insert("LSHandlerRank".into(), association.rank.to_string().into());
1391
1392 Value::Dictionary(dict)
1393 })
1394 .collect::<Vec<_>>();
1395
1396 if exported_associations.is_empty() && document_types.is_empty() {
1397 return None;
1398 }
1399
1400 let mut plist = Dictionary::new();
1401 if !exported_associations.is_empty() {
1402 plist.insert(
1403 "UTExportedTypeDeclarations".into(),
1404 Value::Array(exported_associations),
1405 );
1406 }
1407 if !document_types.is_empty() {
1408 plist.insert("CFBundleDocumentTypes".into(), Value::Array(document_types));
1409 }
1410
1411 Some(Value::Dictionary(plist))
1412}
1413
1414fn extension_to_uti(ext: &str) -> Option<&'static str> {
1416 match ext.to_lowercase().as_str() {
1417 "png" => Some("public.png"),
1419 "jpg" | "jpeg" => Some("public.jpeg"),
1420 "gif" => Some("com.compuserve.gif"),
1421 "bmp" => Some("com.microsoft.bmp"),
1422 "tiff" | "tif" => Some("public.tiff"),
1423 "ico" => Some("com.microsoft.ico"),
1424 "heic" | "heif" => Some("public.heif-standard-image"),
1425 "webp" => Some("org.webmproject.webp"),
1426 "svg" => Some("public.svg-image"),
1427 "mp4" => Some("public.mpeg-4"),
1429 "mov" => Some("com.apple.quicktime-movie"),
1430 "avi" => Some("public.avi"),
1431 "mkv" => Some("public.mpeg-4"),
1432 "mp3" => Some("public.mp3"),
1434 "wav" => Some("com.microsoft.waveform-audio"),
1435 "aac" => Some("public.aac-audio"),
1436 "m4a" => Some("public.mpeg-4-audio"),
1437 "pdf" => Some("com.adobe.pdf"),
1439 "txt" => Some("public.plain-text"),
1440 "rtf" => Some("public.rtf"),
1441 "html" | "htm" => Some("public.html"),
1442 "json" => Some("public.json"),
1443 "xml" => Some("public.xml"),
1444 _ => None,
1445 }
1446}
1447
1448fn mime_type_to_uti(mime_type: &str) -> Option<&'static str> {
1450 match mime_type {
1451 "image/png" => Some("public.png"),
1452 "image/jpeg" | "image/jpg" => Some("public.jpeg"),
1453 "image/gif" => Some("com.compuserve.gif"),
1454 "image/bmp" => Some("com.microsoft.bmp"),
1455 "image/tiff" => Some("public.tiff"),
1456 "image/heic" | "image/heif" => Some("public.heif-standard-image"),
1457 "image/webp" => Some("org.webmproject.webp"),
1458 "image/svg+xml" => Some("public.svg-image"),
1459 mime if mime.starts_with("image/") => Some("public.image"),
1460 "video/mp4" => Some("public.mpeg-4"),
1461 "video/quicktime" => Some("com.apple.quicktime-movie"),
1462 "video/x-msvideo" => Some("public.avi"),
1463 mime if mime.starts_with("video/") => Some("public.movie"),
1464 "audio/mpeg" | "audio/mp3" => Some("public.mp3"),
1465 "audio/wav" | "audio/wave" => Some("com.microsoft.waveform-audio"),
1466 "audio/aac" => Some("public.aac-audio"),
1467 "audio/mp4" => Some("public.mpeg-4-audio"),
1468 mime if mime.starts_with("audio/") => Some("public.audio"),
1469 "application/pdf" => Some("com.adobe.pdf"),
1470 "text/plain" => Some("public.plain-text"),
1471 "text/rtf" => Some("public.rtf"),
1472 "text/html" => Some("public.html"),
1473 "application/json" => Some("public.json"),
1474 "application/xml" | "text/xml" => Some("public.xml"),
1475 _ => None,
1476 }
1477}
1478
1479#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1481#[cfg_attr(feature = "schema", derive(JsonSchema))]
1482#[serde(rename_all = "camelCase", deny_unknown_fields)]
1483pub struct DeepLinkProtocol {
1484 #[serde(default)]
1486 pub schemes: Vec<String>,
1487 #[serde(default)]
1495 pub domains: Vec<String>,
1496 pub name: Option<String>,
1498 #[serde(default)]
1500 pub role: BundleTypeRole,
1501}
1502
1503#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1506#[cfg_attr(feature = "schema", derive(JsonSchema))]
1507#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
1508pub enum BundleResources {
1509 List(Vec<String>),
1511 Map(HashMap<String, String>),
1513}
1514
1515impl BundleResources {
1516 pub fn push(&mut self, path: impl Into<String>) {
1518 match self {
1519 Self::List(l) => l.push(path.into()),
1520 Self::Map(l) => {
1521 let path = path.into();
1522 l.insert(path.clone(), path);
1523 }
1524 }
1525 }
1526}
1527
1528#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1530#[cfg_attr(feature = "schema", derive(JsonSchema))]
1531#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
1532pub enum Updater {
1533 String(V1Compatible),
1535 Bool(bool),
1538}
1539
1540impl Default for Updater {
1541 fn default() -> Self {
1542 Self::Bool(false)
1543 }
1544}
1545
1546#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1548#[cfg_attr(feature = "schema", derive(JsonSchema))]
1549#[serde(rename_all = "camelCase", deny_unknown_fields)]
1550pub enum V1Compatible {
1551 V1Compatible,
1553}
1554
1555#[skip_serializing_none]
1559#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1560#[cfg_attr(feature = "schema", derive(JsonSchema))]
1561#[serde(rename_all = "camelCase", deny_unknown_fields)]
1562pub struct BundleConfig {
1563 #[serde(default)]
1565 pub active: bool,
1566 #[serde(default)]
1568 pub targets: BundleTarget,
1569 #[serde(default)]
1570 pub create_updater_artifacts: Updater,
1572 pub publisher: Option<String>,
1577 pub homepage: Option<String>,
1582 #[serde(default)]
1584 pub icon: Vec<String>,
1585 pub resources: Option<BundleResources>,
1630 pub copyright: Option<String>,
1632 pub license: Option<String>,
1635 #[serde(alias = "license-file")]
1637 pub license_file: Option<PathBuf>,
1638 pub category: Option<String>,
1643 pub file_associations: Option<Vec<FileAssociation>>,
1645 #[serde(alias = "short-description")]
1647 pub short_description: Option<String>,
1648 #[serde(alias = "long-description")]
1650 pub long_description: Option<String>,
1651 #[serde(default, alias = "use-local-tools-dir")]
1659 pub use_local_tools_dir: bool,
1660 #[serde(alias = "external-bin")]
1672 pub external_bin: Option<Vec<String>>,
1673 #[serde(default)]
1675 pub windows: WindowsConfig,
1676 #[serde(default)]
1678 pub linux: LinuxConfig,
1679 #[serde(rename = "macOS", alias = "macos", default)]
1681 pub macos: MacConfig,
1682 #[serde(rename = "iOS", alias = "ios", default)]
1684 pub ios: IosConfig,
1685 #[serde(default)]
1687 pub android: AndroidConfig,
1688}
1689
1690#[derive(Debug, PartialEq, Eq, Serialize, Default, Clone, Copy)]
1692#[serde(rename_all = "camelCase", deny_unknown_fields)]
1693pub struct Color(pub u8, pub u8, pub u8, pub u8);
1694
1695impl From<Color> for (u8, u8, u8, u8) {
1696 fn from(value: Color) -> Self {
1697 (value.0, value.1, value.2, value.3)
1698 }
1699}
1700
1701impl From<Color> for (u8, u8, u8) {
1702 fn from(value: Color) -> Self {
1703 (value.0, value.1, value.2)
1704 }
1705}
1706
1707impl From<(u8, u8, u8, u8)> for Color {
1708 fn from(value: (u8, u8, u8, u8)) -> Self {
1709 Color(value.0, value.1, value.2, value.3)
1710 }
1711}
1712
1713impl From<(u8, u8, u8)> for Color {
1714 fn from(value: (u8, u8, u8)) -> Self {
1715 Color(value.0, value.1, value.2, 255)
1716 }
1717}
1718
1719impl From<Color> for [u8; 4] {
1720 fn from(value: Color) -> Self {
1721 [value.0, value.1, value.2, value.3]
1722 }
1723}
1724
1725impl From<Color> for [u8; 3] {
1726 fn from(value: Color) -> Self {
1727 [value.0, value.1, value.2]
1728 }
1729}
1730
1731impl From<[u8; 4]> for Color {
1732 fn from(value: [u8; 4]) -> Self {
1733 Color(value[0], value[1], value[2], value[3])
1734 }
1735}
1736
1737impl From<[u8; 3]> for Color {
1738 fn from(value: [u8; 3]) -> Self {
1739 Color(value[0], value[1], value[2], 255)
1740 }
1741}
1742
1743impl FromStr for Color {
1744 type Err = String;
1745 fn from_str(mut color: &str) -> Result<Self, Self::Err> {
1746 color = color.trim().strip_prefix('#').unwrap_or(color);
1747 let color = match color.len() {
1748 3 => color.chars()
1750 .flat_map(|c| std::iter::repeat(c).take(2))
1751 .chain(std::iter::repeat('f').take(2))
1752 .collect(),
1753 6 => format!("{color}FF"),
1754 8 => color.to_string(),
1755 _ => return Err("Invalid hex color length, must be either 3, 6 or 8, for example: #fff, #ffffff, or #ffffffff".into()),
1756 };
1757
1758 let r = u8::from_str_radix(&color[0..2], 16).map_err(|e| e.to_string())?;
1759 let g = u8::from_str_radix(&color[2..4], 16).map_err(|e| e.to_string())?;
1760 let b = u8::from_str_radix(&color[4..6], 16).map_err(|e| e.to_string())?;
1761 let a = u8::from_str_radix(&color[6..8], 16).map_err(|e| e.to_string())?;
1762
1763 Ok(Color(r, g, b, a))
1764 }
1765}
1766
1767fn default_alpha() -> u8 {
1768 255
1769}
1770
1771#[derive(Deserialize)]
1772#[cfg_attr(feature = "schema", derive(JsonSchema))]
1773#[serde(untagged)]
1774enum InnerColor {
1775 String(String),
1777 Rgb((u8, u8, u8)),
1779 Rgba((u8, u8, u8, u8)),
1781 RgbaObject {
1783 red: u8,
1784 green: u8,
1785 blue: u8,
1786 #[serde(default = "default_alpha")]
1787 alpha: u8,
1788 },
1789}
1790
1791impl<'de> Deserialize<'de> for Color {
1792 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1793 where
1794 D: Deserializer<'de>,
1795 {
1796 let color = InnerColor::deserialize(deserializer)?;
1797 let color = match color {
1798 InnerColor::String(string) => string.parse().map_err(serde::de::Error::custom)?,
1799 InnerColor::Rgb(rgb) => Color(rgb.0, rgb.1, rgb.2, 255),
1800 InnerColor::Rgba(rgb) => rgb.into(),
1801 InnerColor::RgbaObject {
1802 red,
1803 green,
1804 blue,
1805 alpha,
1806 } => Color(red, green, blue, alpha),
1807 };
1808
1809 Ok(color)
1810 }
1811}
1812
1813#[cfg(feature = "schema")]
1814impl schemars::JsonSchema for Color {
1815 fn schema_name() -> String {
1816 "Color".to_string()
1817 }
1818
1819 fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
1820 let mut schema = schemars::schema_for!(InnerColor).schema;
1821 schema.metadata = None; let any_of = schema.subschemas().any_of.as_mut().unwrap();
1825 let schemars::schema::Schema::Object(str_schema) = any_of.first_mut().unwrap() else {
1826 unreachable!()
1827 };
1828 str_schema.string().pattern = Some("^#?([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$".into());
1829
1830 schema.into()
1831 }
1832}
1833
1834#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1836#[cfg_attr(feature = "schema", derive(JsonSchema))]
1837#[serde(rename_all = "camelCase", deny_unknown_fields)]
1838pub enum BackgroundThrottlingPolicy {
1839 Disabled,
1841 Suspend,
1843 Throttle,
1845}
1846
1847#[skip_serializing_none]
1849#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default)]
1850#[cfg_attr(feature = "schema", derive(JsonSchema))]
1851#[serde(rename_all = "camelCase", deny_unknown_fields)]
1852pub struct WindowEffectsConfig {
1853 pub effects: Vec<WindowEffect>,
1856 pub state: Option<WindowEffectState>,
1858 pub radius: Option<f64>,
1860 pub color: Option<Color>,
1863}
1864
1865#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default)]
1868#[cfg_attr(feature = "schema", derive(JsonSchema))]
1869#[serde(rename_all = "camelCase", deny_unknown_fields)]
1870pub struct PreventOverflowMargin {
1871 pub width: u32,
1873 pub height: u32,
1875}
1876
1877#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1879#[cfg_attr(feature = "schema", derive(JsonSchema))]
1880#[serde(untagged)]
1881pub enum PreventOverflowConfig {
1882 Enable(bool),
1884 Margin(PreventOverflowMargin),
1887}
1888
1889#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Default)]
1895#[cfg_attr(feature = "schema", derive(JsonSchema))]
1896#[serde(rename_all = "camelCase", deny_unknown_fields)]
1897#[non_exhaustive]
1898pub enum ScrollBarStyle {
1899 #[default]
1900 Default,
1902
1903 FluentOverlay,
1908}
1909
1910#[skip_serializing_none]
1914#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
1915#[cfg_attr(feature = "schema", derive(JsonSchema))]
1916#[serde(rename_all = "camelCase", deny_unknown_fields)]
1917pub struct WindowConfig {
1918 #[serde(default = "default_window_label")]
1920 pub label: String,
1921 #[serde(default = "default_true")]
1936 pub create: bool,
1937 #[serde(default)]
1939 pub url: WebviewUrl,
1940 #[serde(alias = "user-agent")]
1942 pub user_agent: Option<String>,
1943 #[serde(default = "default_true", alias = "drag-drop-enabled")]
1947 pub drag_drop_enabled: bool,
1948 #[serde(default)]
1950 pub center: bool,
1951 pub x: Option<f64>,
1953 pub y: Option<f64>,
1955 #[serde(default = "default_width")]
1957 pub width: f64,
1958 #[serde(default = "default_height")]
1960 pub height: f64,
1961 #[serde(alias = "min-width")]
1963 pub min_width: Option<f64>,
1964 #[serde(alias = "min-height")]
1966 pub min_height: Option<f64>,
1967 #[serde(alias = "max-width")]
1969 pub max_width: Option<f64>,
1970 #[serde(alias = "max-height")]
1972 pub max_height: Option<f64>,
1973 #[serde(alias = "prevent-overflow")]
1979 pub prevent_overflow: Option<PreventOverflowConfig>,
1980 #[serde(default = "default_true")]
1982 pub resizable: bool,
1983 #[serde(default = "default_true")]
1991 pub maximizable: bool,
1992 #[serde(default = "default_true")]
1998 pub minimizable: bool,
1999 #[serde(default = "default_true")]
2007 pub closable: bool,
2008 #[serde(default = "default_title")]
2010 pub title: String,
2011 #[serde(default)]
2013 pub fullscreen: bool,
2014 #[serde(default = "default_true")]
2016 pub focus: bool,
2017 #[serde(default = "default_true")]
2019 pub focusable: bool,
2020 #[serde(default)]
2025 pub transparent: bool,
2026 #[serde(default)]
2028 pub maximized: bool,
2029 #[serde(default = "default_true")]
2031 pub visible: bool,
2032 #[serde(default = "default_true")]
2034 pub decorations: bool,
2035 #[serde(default, alias = "always-on-bottom")]
2037 pub always_on_bottom: bool,
2038 #[serde(default, alias = "always-on-top")]
2040 pub always_on_top: bool,
2041 #[serde(default, alias = "visible-on-all-workspaces")]
2047 pub visible_on_all_workspaces: bool,
2048 #[serde(default, alias = "content-protected")]
2050 pub content_protected: bool,
2051 #[serde(default, alias = "skip-taskbar")]
2053 pub skip_taskbar: bool,
2054 pub window_classname: Option<String>,
2056 pub theme: Option<crate::Theme>,
2058 #[serde(default, alias = "title-bar-style")]
2060 pub title_bar_style: TitleBarStyle,
2061 #[serde(default, alias = "traffic-light-position")]
2065 pub traffic_light_position: Option<LogicalPosition>,
2066 #[serde(default, alias = "hidden-title")]
2068 pub hidden_title: bool,
2069 #[serde(default, alias = "accept-first-mouse")]
2071 pub accept_first_mouse: bool,
2072 #[serde(default, alias = "tabbing-identifier")]
2079 pub tabbing_identifier: Option<String>,
2080 #[serde(default, alias = "additional-browser-args")]
2083 pub additional_browser_args: Option<String>,
2084 #[serde(default = "default_true")]
2094 pub shadow: bool,
2095 #[serde(default, alias = "window-effects")]
2104 pub window_effects: Option<WindowEffectsConfig>,
2105 #[serde(default)]
2111 pub incognito: bool,
2112 pub parent: Option<String>,
2124 #[serde(alias = "proxy-url")]
2132 pub proxy_url: Option<Url>,
2133 #[serde(default, alias = "zoom-hotkeys-enabled")]
2143 pub zoom_hotkeys_enabled: bool,
2144 #[serde(default, alias = "browser-extensions-enabled")]
2151 pub browser_extensions_enabled: bool,
2152
2153 #[serde(default, alias = "use-https-scheme")]
2163 pub use_https_scheme: bool,
2164 pub devtools: Option<bool>,
2174
2175 #[serde(alias = "background-color")]
2183 pub background_color: Option<Color>,
2184
2185 #[serde(default, alias = "background-throttling")]
2200 pub background_throttling: Option<BackgroundThrottlingPolicy>,
2201 #[serde(default, alias = "javascript-disabled")]
2203 pub javascript_disabled: bool,
2204 #[serde(default = "default_true", alias = "allow-link-preview")]
2207 pub allow_link_preview: bool,
2208 #[serde(
2213 default,
2214 alias = "disable-input-accessory-view",
2215 alias = "disable_input_accessory_view"
2216 )]
2217 pub disable_input_accessory_view: bool,
2218 #[serde(default, alias = "data-directory")]
2229 pub data_directory: Option<PathBuf>,
2230 #[serde(default, alias = "data-store-identifier")]
2242 pub data_store_identifier: Option<[u8; 16]>,
2243
2244 #[serde(default, alias = "scroll-bar-style")]
2257 pub scroll_bar_style: ScrollBarStyle,
2258 #[serde(default, alias = "activity-name")]
2260 pub activity_name: Option<String>,
2261 #[serde(default, alias = "created-by-activity-name")]
2265 pub created_by_activity_name: Option<String>,
2266
2267 #[serde(default, alias = "requested-by-scene-identifier")]
2272 pub requested_by_scene_identifier: Option<String>,
2273 #[serde(default = "default_true", alias = "general-autofill-enabled")]
2291 pub general_autofill_enabled: bool,
2292}
2293
2294impl Default for WindowConfig {
2295 fn default() -> Self {
2296 Self {
2297 label: default_window_label(),
2298 url: WebviewUrl::default(),
2299 create: true,
2300 user_agent: None,
2301 drag_drop_enabled: true,
2302 center: false,
2303 x: None,
2304 y: None,
2305 width: default_width(),
2306 height: default_height(),
2307 min_width: None,
2308 min_height: None,
2309 max_width: None,
2310 max_height: None,
2311 prevent_overflow: None,
2312 resizable: true,
2313 maximizable: true,
2314 minimizable: true,
2315 closable: true,
2316 title: default_title(),
2317 fullscreen: false,
2318 focus: true,
2319 focusable: true,
2320 transparent: false,
2321 maximized: false,
2322 visible: true,
2323 decorations: true,
2324 always_on_bottom: false,
2325 always_on_top: false,
2326 visible_on_all_workspaces: false,
2327 content_protected: false,
2328 skip_taskbar: false,
2329 window_classname: None,
2330 theme: None,
2331 title_bar_style: Default::default(),
2332 traffic_light_position: None,
2333 hidden_title: false,
2334 accept_first_mouse: false,
2335 tabbing_identifier: None,
2336 additional_browser_args: None,
2337 shadow: true,
2338 window_effects: None,
2339 incognito: false,
2340 parent: None,
2341 proxy_url: None,
2342 zoom_hotkeys_enabled: false,
2343 browser_extensions_enabled: false,
2344 use_https_scheme: false,
2345 devtools: None,
2346 background_color: None,
2347 background_throttling: None,
2348 javascript_disabled: false,
2349 allow_link_preview: true,
2350 disable_input_accessory_view: false,
2351 data_directory: None,
2352 data_store_identifier: None,
2353 scroll_bar_style: ScrollBarStyle::Default,
2354 activity_name: None,
2355 created_by_activity_name: None,
2356 requested_by_scene_identifier: None,
2357 general_autofill_enabled: true,
2358 }
2359 }
2360}
2361
2362fn default_window_label() -> String {
2363 "main".to_string()
2364}
2365
2366fn default_width() -> f64 {
2367 800.
2368}
2369
2370fn default_height() -> f64 {
2371 600.
2372}
2373
2374fn default_title() -> String {
2375 "Tauri App".to_string()
2376}
2377
2378#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2381#[cfg_attr(feature = "schema", derive(JsonSchema))]
2382#[serde(rename_all = "camelCase", untagged)]
2383pub enum CspDirectiveSources {
2384 Inline(String),
2386 List(Vec<String>),
2388}
2389
2390impl Default for CspDirectiveSources {
2391 fn default() -> Self {
2392 Self::List(Vec::new())
2393 }
2394}
2395
2396impl From<CspDirectiveSources> for Vec<String> {
2397 fn from(sources: CspDirectiveSources) -> Self {
2398 match sources {
2399 CspDirectiveSources::Inline(source) => source.split(' ').map(|s| s.to_string()).collect(),
2400 CspDirectiveSources::List(l) => l,
2401 }
2402 }
2403}
2404
2405impl CspDirectiveSources {
2406 pub fn contains(&self, source: &str) -> bool {
2408 match self {
2409 Self::Inline(s) => s.contains(&format!("{source} ")) || s.contains(&format!(" {source}")),
2410 Self::List(l) => l.contains(&source.into()),
2411 }
2412 }
2413
2414 pub fn push<S: AsRef<str>>(&mut self, source: S) {
2416 match self {
2417 Self::Inline(s) => {
2418 s.push(' ');
2419 s.push_str(source.as_ref());
2420 }
2421 Self::List(l) => {
2422 l.push(source.as_ref().to_string());
2423 }
2424 }
2425 }
2426
2427 pub fn extend(&mut self, sources: Vec<String>) {
2429 for s in sources {
2430 self.push(s);
2431 }
2432 }
2433}
2434
2435#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2438#[cfg_attr(feature = "schema", derive(JsonSchema))]
2439#[serde(rename_all = "camelCase", untagged)]
2440pub enum Csp {
2441 Policy(String),
2443 DirectiveMap(HashMap<String, CspDirectiveSources>),
2445}
2446
2447impl From<HashMap<String, CspDirectiveSources>> for Csp {
2448 fn from(map: HashMap<String, CspDirectiveSources>) -> Self {
2449 Self::DirectiveMap(map)
2450 }
2451}
2452
2453impl From<Csp> for HashMap<String, CspDirectiveSources> {
2454 fn from(csp: Csp) -> Self {
2455 match csp {
2456 Csp::Policy(policy) => {
2457 let mut map = HashMap::new();
2458 for directive in policy.split(';') {
2459 let mut tokens = directive.trim().split(' ');
2460 if let Some(directive) = tokens.next() {
2461 let sources = tokens.map(|s| s.to_string()).collect::<Vec<String>>();
2462 map.insert(directive.to_string(), CspDirectiveSources::List(sources));
2463 }
2464 }
2465 map
2466 }
2467 Csp::DirectiveMap(m) => m,
2468 }
2469 }
2470}
2471
2472impl Display for Csp {
2473 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2474 match self {
2475 Self::Policy(s) => write!(f, "{s}"),
2476 Self::DirectiveMap(m) => {
2477 let len = m.len();
2478 let mut i = 0;
2479 for (directive, sources) in m {
2480 let sources: Vec<String> = sources.clone().into();
2481 write!(f, "{} {}", directive, sources.join(" "))?;
2482 i += 1;
2483 if i != len {
2484 write!(f, "; ")?;
2485 }
2486 }
2487 Ok(())
2488 }
2489 }
2490 }
2491}
2492
2493#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2495#[serde(untagged)]
2496#[cfg_attr(feature = "schema", derive(JsonSchema))]
2497pub enum DisabledCspModificationKind {
2498 Flag(bool),
2501 List(Vec<String>),
2503}
2504
2505impl DisabledCspModificationKind {
2506 pub fn can_modify(&self, directive: &str) -> bool {
2508 match self {
2509 Self::Flag(f) => !f,
2510 Self::List(l) => !l.contains(&directive.into()),
2511 }
2512 }
2513}
2514
2515impl Default for DisabledCspModificationKind {
2516 fn default() -> Self {
2517 Self::Flag(false)
2518 }
2519}
2520
2521#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2530#[serde(untagged)]
2531#[cfg_attr(feature = "schema", derive(JsonSchema))]
2532pub enum FsScope {
2533 AllowedPaths(Vec<PathBuf>),
2535 #[serde(rename_all = "camelCase")]
2537 Scope {
2538 #[serde(default)]
2540 allow: Vec<PathBuf>,
2541 #[serde(default)]
2544 deny: Vec<PathBuf>,
2545 #[serde(alias = "require-literal-leading-dot")]
2554 require_literal_leading_dot: Option<bool>,
2555 },
2556}
2557
2558impl Default for FsScope {
2559 fn default() -> Self {
2560 Self::AllowedPaths(Vec::new())
2561 }
2562}
2563
2564impl FsScope {
2565 pub fn allowed_paths(&self) -> &Vec<PathBuf> {
2567 match self {
2568 Self::AllowedPaths(p) => p,
2569 Self::Scope { allow, .. } => allow,
2570 }
2571 }
2572
2573 pub fn forbidden_paths(&self) -> Option<&Vec<PathBuf>> {
2575 match self {
2576 Self::AllowedPaths(_) => None,
2577 Self::Scope { deny, .. } => Some(deny),
2578 }
2579 }
2580}
2581
2582#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2586#[cfg_attr(feature = "schema", derive(JsonSchema))]
2587#[serde(rename_all = "camelCase", deny_unknown_fields)]
2588pub struct AssetProtocolConfig {
2589 #[serde(default)]
2591 pub scope: FsScope,
2592 #[serde(default)]
2594 pub enable: bool,
2595}
2596
2597#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2601#[cfg_attr(feature = "schema", derive(JsonSchema))]
2602#[serde(rename_all = "camelCase", untagged)]
2603pub enum HeaderSource {
2604 Inline(String),
2606 List(Vec<String>),
2608 Map(HashMap<String, String>),
2610}
2611
2612impl Display for HeaderSource {
2613 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2614 match self {
2615 Self::Inline(s) => write!(f, "{s}"),
2616 Self::List(l) => write!(f, "{}", l.join(", ")),
2617 Self::Map(m) => {
2618 let len = m.len();
2619 let mut i = 0;
2620 for (key, value) in m {
2621 write!(f, "{key} {value}")?;
2622 i += 1;
2623 if i != len {
2624 write!(f, "; ")?;
2625 }
2626 }
2627 Ok(())
2628 }
2629 }
2630 }
2631}
2632
2633pub trait HeaderAddition {
2637 fn add_configured_headers(self, headers: Option<&HeaderConfig>) -> http::response::Builder;
2639}
2640
2641impl HeaderAddition for Builder {
2642 fn add_configured_headers(mut self, headers: Option<&HeaderConfig>) -> http::response::Builder {
2646 if let Some(headers) = headers {
2647 if let Some(value) = &headers.access_control_allow_credentials {
2649 self = self.header("Access-Control-Allow-Credentials", value.to_string());
2650 };
2651
2652 if let Some(value) = &headers.access_control_allow_headers {
2654 self = self.header("Access-Control-Allow-Headers", value.to_string());
2655 };
2656
2657 if let Some(value) = &headers.access_control_allow_methods {
2659 self = self.header("Access-Control-Allow-Methods", value.to_string());
2660 };
2661
2662 if let Some(value) = &headers.access_control_expose_headers {
2664 self = self.header("Access-Control-Expose-Headers", value.to_string());
2665 };
2666
2667 if let Some(value) = &headers.access_control_max_age {
2669 self = self.header("Access-Control-Max-Age", value.to_string());
2670 };
2671
2672 if let Some(value) = &headers.cross_origin_embedder_policy {
2674 self = self.header("Cross-Origin-Embedder-Policy", value.to_string());
2675 };
2676
2677 if let Some(value) = &headers.cross_origin_opener_policy {
2679 self = self.header("Cross-Origin-Opener-Policy", value.to_string());
2680 };
2681
2682 if let Some(value) = &headers.cross_origin_resource_policy {
2684 self = self.header("Cross-Origin-Resource-Policy", value.to_string());
2685 };
2686
2687 if let Some(value) = &headers.permissions_policy {
2689 self = self.header("Permission-Policy", value.to_string());
2690 };
2691
2692 if let Some(value) = &headers.service_worker_allowed {
2693 self = self.header("Service-Worker-Allowed", value.to_string());
2694 }
2695
2696 if let Some(value) = &headers.timing_allow_origin {
2698 self = self.header("Timing-Allow-Origin", value.to_string());
2699 };
2700
2701 if let Some(value) = &headers.x_content_type_options {
2703 self = self.header("X-Content-Type-Options", value.to_string());
2704 };
2705
2706 if let Some(value) = &headers.tauri_custom_header {
2708 self = self.header("Tauri-Custom-Header", value.to_string());
2710 };
2711 }
2712 self
2713 }
2714}
2715
2716#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2768#[cfg_attr(feature = "schema", derive(JsonSchema))]
2769#[serde(deny_unknown_fields)]
2770pub struct HeaderConfig {
2771 #[serde(rename = "Access-Control-Allow-Credentials")]
2776 pub access_control_allow_credentials: Option<HeaderSource>,
2777 #[serde(rename = "Access-Control-Allow-Headers")]
2785 pub access_control_allow_headers: Option<HeaderSource>,
2786 #[serde(rename = "Access-Control-Allow-Methods")]
2791 pub access_control_allow_methods: Option<HeaderSource>,
2792 #[serde(rename = "Access-Control-Expose-Headers")]
2798 pub access_control_expose_headers: Option<HeaderSource>,
2799 #[serde(rename = "Access-Control-Max-Age")]
2806 pub access_control_max_age: Option<HeaderSource>,
2807 #[serde(rename = "Cross-Origin-Embedder-Policy")]
2812 pub cross_origin_embedder_policy: Option<HeaderSource>,
2813 #[serde(rename = "Cross-Origin-Opener-Policy")]
2820 pub cross_origin_opener_policy: Option<HeaderSource>,
2821 #[serde(rename = "Cross-Origin-Resource-Policy")]
2826 pub cross_origin_resource_policy: Option<HeaderSource>,
2827 #[serde(rename = "Permissions-Policy")]
2832 pub permissions_policy: Option<HeaderSource>,
2833 #[serde(rename = "Service-Worker-Allowed")]
2843 pub service_worker_allowed: Option<HeaderSource>,
2844 #[serde(rename = "Timing-Allow-Origin")]
2850 pub timing_allow_origin: Option<HeaderSource>,
2851 #[serde(rename = "X-Content-Type-Options")]
2858 pub x_content_type_options: Option<HeaderSource>,
2859 #[serde(rename = "Tauri-Custom-Header")]
2864 pub tauri_custom_header: Option<HeaderSource>,
2865}
2866
2867impl HeaderConfig {
2868 pub fn new() -> Self {
2870 HeaderConfig {
2871 access_control_allow_credentials: None,
2872 access_control_allow_methods: None,
2873 access_control_allow_headers: None,
2874 access_control_expose_headers: None,
2875 access_control_max_age: None,
2876 cross_origin_embedder_policy: None,
2877 cross_origin_opener_policy: None,
2878 cross_origin_resource_policy: None,
2879 permissions_policy: None,
2880 service_worker_allowed: None,
2881 timing_allow_origin: None,
2882 x_content_type_options: None,
2883 tauri_custom_header: None,
2884 }
2885 }
2886}
2887
2888#[skip_serializing_none]
2892#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
2893#[cfg_attr(feature = "schema", derive(JsonSchema))]
2894#[serde(rename_all = "camelCase", deny_unknown_fields)]
2895pub struct SecurityConfig {
2896 pub csp: Option<Csp>,
2902 #[serde(alias = "dev-csp")]
2907 pub dev_csp: Option<Csp>,
2908 #[serde(default, alias = "freeze-prototype")]
2910 pub freeze_prototype: bool,
2911 #[serde(default, alias = "dangerous-disable-asset-csp-modification")]
2924 pub dangerous_disable_asset_csp_modification: DisabledCspModificationKind,
2925 #[serde(default, alias = "asset-protocol")]
2927 pub asset_protocol: AssetProtocolConfig,
2928 #[serde(default)]
2930 pub pattern: PatternKind,
2931 #[serde(default)]
2954 pub capabilities: Vec<CapabilityEntry>,
2955 #[serde(default)]
2958 pub headers: Option<HeaderConfig>,
2959}
2960
2961#[derive(Debug, Clone, PartialEq, Serialize)]
2963#[cfg_attr(feature = "schema", derive(JsonSchema))]
2964#[serde(untagged)]
2965pub enum CapabilityEntry {
2966 Inlined(Capability),
2968 Reference(String),
2970}
2971
2972impl<'de> Deserialize<'de> for CapabilityEntry {
2973 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2974 where
2975 D: Deserializer<'de>,
2976 {
2977 UntaggedEnumVisitor::new()
2978 .string(|string| Ok(Self::Reference(string.to_owned())))
2979 .map(|map| map.deserialize::<Capability>().map(Self::Inlined))
2980 .deserialize(deserializer)
2981 }
2982}
2983
2984#[skip_serializing_none]
2986#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default)]
2987#[serde(rename_all = "lowercase", tag = "use", content = "options")]
2988#[cfg_attr(feature = "schema", derive(JsonSchema))]
2989pub enum PatternKind {
2990 #[default]
2992 Brownfield,
2993 Isolation {
2995 dir: PathBuf,
2997 },
2998}
2999
3000#[skip_serializing_none]
3004#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
3005#[cfg_attr(feature = "schema", derive(JsonSchema))]
3006#[serde(rename_all = "camelCase", deny_unknown_fields)]
3007pub struct AppConfig {
3008 #[serde(default)]
3063 pub windows: Vec<WindowConfig>,
3064 #[serde(default)]
3066 pub security: SecurityConfig,
3067 #[serde(alias = "tray-icon")]
3069 pub tray_icon: Option<TrayIconConfig>,
3070 #[serde(rename = "macOSPrivateApi", alias = "macos-private-api", default)]
3072 pub macos_private_api: bool,
3073 #[serde(default, alias = "with-global-tauri")]
3075 pub with_global_tauri: bool,
3076 #[serde(rename = "enableGTKAppId", alias = "enable-gtk-app-id", default)]
3078 pub enable_gtk_app_id: bool,
3079}
3080
3081impl AppConfig {
3082 pub fn all_features() -> Vec<&'static str> {
3084 vec![
3085 "tray-icon",
3086 "macos-private-api",
3087 "protocol-asset",
3088 "isolation",
3089 ]
3090 }
3091
3092 pub fn features(&self) -> Vec<&str> {
3094 let mut features = Vec::new();
3095 if self.tray_icon.is_some() {
3096 features.push("tray-icon");
3097 }
3098 if self.macos_private_api {
3099 features.push("macos-private-api");
3100 }
3101 if self.security.asset_protocol.enable {
3102 features.push("protocol-asset");
3103 }
3104
3105 if let PatternKind::Isolation { .. } = self.security.pattern {
3106 features.push("isolation");
3107 }
3108
3109 features.sort_unstable();
3110 features
3111 }
3112}
3113
3114#[skip_serializing_none]
3118#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
3119#[cfg_attr(feature = "schema", derive(JsonSchema))]
3120#[serde(rename_all = "camelCase", deny_unknown_fields)]
3121pub struct TrayIconConfig {
3122 pub id: Option<String>,
3124 #[serde(alias = "icon-path")]
3130 pub icon_path: PathBuf,
3131 #[serde(default, alias = "icon-as-template")]
3133 pub icon_as_template: bool,
3134 #[serde(default = "default_true", alias = "menu-on-left-click")]
3140 #[deprecated(since = "2.2.0", note = "Use `show_menu_on_left_click` instead.")]
3141 pub menu_on_left_click: bool,
3142 #[serde(default = "default_true", alias = "show-menu-on-left-click")]
3148 pub show_menu_on_left_click: bool,
3149 pub title: Option<String>,
3151 pub tooltip: Option<String>,
3153}
3154
3155#[skip_serializing_none]
3157#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
3158#[cfg_attr(feature = "schema", derive(JsonSchema))]
3159#[serde(rename_all = "camelCase", deny_unknown_fields)]
3160pub struct IosConfig {
3161 pub template: Option<PathBuf>,
3165 pub frameworks: Option<Vec<String>>,
3169 #[serde(alias = "development-team")]
3172 pub development_team: Option<String>,
3173 #[serde(alias = "bundle-version")]
3177 pub bundle_version: Option<String>,
3178 #[serde(
3182 alias = "minimum-system-version",
3183 default = "ios_minimum_system_version"
3184 )]
3185 pub minimum_system_version: String,
3186 #[serde(alias = "info-plist")]
3190 pub info_plist: Option<PathBuf>,
3191}
3192
3193impl Default for IosConfig {
3194 fn default() -> Self {
3195 Self {
3196 template: None,
3197 frameworks: None,
3198 development_team: None,
3199 bundle_version: None,
3200 minimum_system_version: ios_minimum_system_version(),
3201 info_plist: None,
3202 }
3203 }
3204}
3205
3206#[skip_serializing_none]
3208#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
3209#[cfg_attr(feature = "schema", derive(JsonSchema))]
3210#[serde(rename_all = "camelCase", deny_unknown_fields)]
3211pub struct AndroidConfig {
3212 #[serde(alias = "min-sdk-version", default = "default_min_sdk_version")]
3215 pub min_sdk_version: u32,
3216
3217 #[serde(alias = "version-code")]
3223 #[cfg_attr(feature = "schema", validate(range(min = 1, max = 2_100_000_000)))]
3224 pub version_code: Option<u32>,
3225
3226 #[serde(alias = "auto-increment-version-code", default)]
3234 pub auto_increment_version_code: bool,
3235
3236 #[serde(alias = "debug-application-id-suffix")]
3240 pub debug_application_id_suffix: Option<String>,
3241}
3242
3243impl Default for AndroidConfig {
3244 fn default() -> Self {
3245 Self {
3246 min_sdk_version: default_min_sdk_version(),
3247 version_code: None,
3248 auto_increment_version_code: false,
3249 debug_application_id_suffix: None,
3250 }
3251 }
3252}
3253
3254fn default_min_sdk_version() -> u32 {
3255 24
3256}
3257
3258#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
3260#[cfg_attr(feature = "schema", derive(JsonSchema))]
3261#[serde(untagged, deny_unknown_fields)]
3262#[non_exhaustive]
3263pub enum FrontendDist {
3264 Url(Url),
3266 Directory(PathBuf),
3268 Files(Vec<PathBuf>),
3270}
3271
3272impl std::fmt::Display for FrontendDist {
3273 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3274 match self {
3275 Self::Url(url) => write!(f, "{url}"),
3276 Self::Directory(p) => write!(f, "{}", p.display()),
3277 Self::Files(files) => write!(f, "{}", serde_json::to_string(files).unwrap()),
3278 }
3279 }
3280}
3281
3282#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
3284#[cfg_attr(feature = "schema", derive(JsonSchema))]
3285#[serde(rename_all = "camelCase", untagged)]
3286pub enum BeforeDevCommand {
3287 Script(String),
3289 ScriptWithOptions {
3291 script: String,
3293 cwd: Option<String>,
3295 #[serde(default)]
3297 wait: bool,
3298 },
3299}
3300
3301#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
3303#[cfg_attr(feature = "schema", derive(JsonSchema))]
3304#[serde(rename_all = "camelCase", untagged)]
3305pub enum HookCommand {
3306 Script(String),
3308 ScriptWithOptions {
3310 script: String,
3312 cwd: Option<String>,
3314 },
3315}
3316
3317#[skip_serializing_none]
3319#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
3320#[cfg_attr(feature = "schema", derive(JsonSchema))]
3321#[serde(untagged)]
3322pub enum RunnerConfig {
3323 String(String),
3325 Object {
3327 cmd: String,
3329 cwd: Option<String>,
3331 args: Option<Vec<String>>,
3333 },
3334}
3335
3336impl Default for RunnerConfig {
3337 fn default() -> Self {
3338 RunnerConfig::String("cargo".to_string())
3339 }
3340}
3341
3342impl RunnerConfig {
3343 pub fn cmd(&self) -> &str {
3345 match self {
3346 RunnerConfig::String(cmd) => cmd,
3347 RunnerConfig::Object { cmd, .. } => cmd,
3348 }
3349 }
3350
3351 pub fn cwd(&self) -> Option<&str> {
3353 match self {
3354 RunnerConfig::String(_) => None,
3355 RunnerConfig::Object { cwd, .. } => cwd.as_deref(),
3356 }
3357 }
3358
3359 pub fn args(&self) -> Option<&[String]> {
3361 match self {
3362 RunnerConfig::String(_) => None,
3363 RunnerConfig::Object { args, .. } => args.as_deref(),
3364 }
3365 }
3366}
3367
3368impl std::str::FromStr for RunnerConfig {
3369 type Err = std::convert::Infallible;
3370
3371 fn from_str(s: &str) -> Result<Self, Self::Err> {
3372 Ok(RunnerConfig::String(s.to_string()))
3373 }
3374}
3375
3376impl From<&str> for RunnerConfig {
3377 fn from(s: &str) -> Self {
3378 RunnerConfig::String(s.to_string())
3379 }
3380}
3381
3382impl From<String> for RunnerConfig {
3383 fn from(s: String) -> Self {
3384 RunnerConfig::String(s)
3385 }
3386}
3387
3388#[skip_serializing_none]
3392#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Default)]
3393#[cfg_attr(feature = "schema", derive(JsonSchema))]
3394#[serde(rename_all = "camelCase", deny_unknown_fields)]
3395pub struct BuildConfig {
3396 pub runner: Option<RunnerConfig>,
3398 #[serde(alias = "dev-url")]
3406 pub dev_url: Option<Url>,
3407 #[serde(alias = "frontend-dist")]
3421 pub frontend_dist: Option<FrontendDist>,
3422 #[serde(alias = "before-dev-command")]
3426 pub before_dev_command: Option<BeforeDevCommand>,
3427 #[serde(alias = "before-build-command")]
3431 pub before_build_command: Option<HookCommand>,
3432 #[serde(alias = "before-bundle-command")]
3436 pub before_bundle_command: Option<HookCommand>,
3437 pub features: Option<Vec<String>>,
3439 #[serde(alias = "remove-unused-commands", default)]
3447 pub remove_unused_commands: bool,
3448 #[serde(alias = "additional-watch-directories", default)]
3450 pub additional_watch_folders: Vec<PathBuf>,
3451}
3452
3453#[derive(Debug, PartialEq, Eq)]
3454struct PackageVersion(String);
3455
3456impl<'d> serde::Deserialize<'d> for PackageVersion {
3457 fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {
3458 struct PackageVersionVisitor;
3459
3460 impl Visitor<'_> for PackageVersionVisitor {
3461 type Value = PackageVersion;
3462
3463 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
3464 write!(
3465 formatter,
3466 "a semver string or a path to a package.json file"
3467 )
3468 }
3469
3470 fn visit_str<E: DeError>(self, value: &str) -> Result<PackageVersion, E> {
3471 let path = PathBuf::from(value);
3472 if path.exists() {
3473 let json_str = read_to_string(&path)
3474 .map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?;
3475 let package_json: serde_json::Value = serde_json::from_str(&json_str)
3476 .map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?;
3477 if let Some(obj) = package_json.as_object() {
3478 let version = obj
3479 .get("version")
3480 .ok_or_else(|| DeError::custom("JSON must contain a `version` field"))?
3481 .as_str()
3482 .ok_or_else(|| {
3483 DeError::custom(format!("`{} > version` must be a string", path.display()))
3484 })?;
3485 Ok(PackageVersion(
3486 Version::from_str(version)
3487 .map_err(|_| {
3488 DeError::custom("`tauri.conf.json > version` must be a semver string")
3489 })?
3490 .to_string(),
3491 ))
3492 } else {
3493 Err(DeError::custom(
3494 "`tauri.conf.json > version` value is not a path to a JSON object",
3495 ))
3496 }
3497 } else {
3498 Ok(PackageVersion(
3499 Version::from_str(value)
3500 .map_err(|_| DeError::custom("`tauri.conf.json > version` must be a semver string"))?
3501 .to_string(),
3502 ))
3503 }
3504 }
3505 }
3506
3507 deserializer.deserialize_string(PackageVersionVisitor {})
3508 }
3509}
3510
3511fn version_deserializer<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
3512where
3513 D: Deserializer<'de>,
3514{
3515 Option::<PackageVersion>::deserialize(deserializer).map(|v| v.map(|v| v.0))
3516}
3517
3518#[skip_serializing_none]
3584#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
3585#[cfg_attr(feature = "schema", derive(JsonSchema))]
3586#[serde(rename_all = "camelCase", deny_unknown_fields)]
3587pub struct Config {
3588 #[serde(rename = "$schema")]
3590 pub schema: Option<String>,
3591 #[serde(alias = "product-name")]
3593 #[cfg_attr(feature = "schema", validate(regex(pattern = "^[^/\\:*?\"<>|]+$")))]
3594 pub product_name: Option<String>,
3595 #[serde(alias = "main-binary-name")]
3609 pub main_binary_name: Option<String>,
3610 #[serde(deserialize_with = "version_deserializer", default)]
3626 pub version: Option<String>,
3627 pub identifier: String,
3633 #[serde(default)]
3635 pub app: AppConfig,
3636 #[serde(default)]
3638 pub build: BuildConfig,
3639 #[serde(default)]
3641 pub bundle: BundleConfig,
3642 #[serde(default)]
3644 pub plugins: PluginConfig,
3645}
3646
3647#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
3651#[cfg_attr(feature = "schema", derive(JsonSchema))]
3652pub struct PluginConfig(pub HashMap<String, JsonValue>);
3653
3654#[cfg(any(feature = "build", feature = "build-2"))]
3660mod build {
3661 use super::*;
3662 use crate::{literal_struct, tokens::*};
3663 use proc_macro2::TokenStream;
3664 use quote::{quote, ToTokens, TokenStreamExt};
3665 use std::convert::identity;
3666
3667 impl ToTokens for WebviewUrl {
3668 fn to_tokens(&self, tokens: &mut TokenStream) {
3669 let prefix = quote! { ::tauri::utils::config::WebviewUrl };
3670
3671 tokens.append_all(match self {
3672 Self::App(path) => {
3673 let path = path_buf_lit(path);
3674 quote! { #prefix::App(#path) }
3675 }
3676 Self::External(url) => {
3677 let url = url_lit(url);
3678 quote! { #prefix::External(#url) }
3679 }
3680 Self::CustomProtocol(url) => {
3681 let url = url_lit(url);
3682 quote! { #prefix::CustomProtocol(#url) }
3683 }
3684 })
3685 }
3686 }
3687
3688 impl ToTokens for BackgroundThrottlingPolicy {
3689 fn to_tokens(&self, tokens: &mut TokenStream) {
3690 let prefix = quote! { ::tauri::utils::config::BackgroundThrottlingPolicy };
3691 tokens.append_all(match self {
3692 Self::Disabled => quote! { #prefix::Disabled },
3693 Self::Throttle => quote! { #prefix::Throttle },
3694 Self::Suspend => quote! { #prefix::Suspend },
3695 })
3696 }
3697 }
3698
3699 impl ToTokens for crate::Theme {
3700 fn to_tokens(&self, tokens: &mut TokenStream) {
3701 let prefix = quote! { ::tauri::utils::Theme };
3702
3703 tokens.append_all(match self {
3704 Self::Light => quote! { #prefix::Light },
3705 Self::Dark => quote! { #prefix::Dark },
3706 })
3707 }
3708 }
3709
3710 impl ToTokens for Color {
3711 fn to_tokens(&self, tokens: &mut TokenStream) {
3712 let Color(r, g, b, a) = self;
3713 tokens.append_all(quote! {::tauri::utils::config::Color(#r,#g,#b,#a)});
3714 }
3715 }
3716 impl ToTokens for WindowEffectsConfig {
3717 fn to_tokens(&self, tokens: &mut TokenStream) {
3718 let effects = vec_lit(self.effects.clone(), |d| d);
3719 let state = opt_lit(self.state.as_ref());
3720 let radius = opt_lit(self.radius.as_ref());
3721 let color = opt_lit(self.color.as_ref());
3722
3723 literal_struct!(
3724 tokens,
3725 ::tauri::utils::config::WindowEffectsConfig,
3726 effects,
3727 state,
3728 radius,
3729 color
3730 )
3731 }
3732 }
3733
3734 impl ToTokens for crate::TitleBarStyle {
3735 fn to_tokens(&self, tokens: &mut TokenStream) {
3736 let prefix = quote! { ::tauri::utils::TitleBarStyle };
3737
3738 tokens.append_all(match self {
3739 Self::Visible => quote! { #prefix::Visible },
3740 Self::Transparent => quote! { #prefix::Transparent },
3741 Self::Overlay => quote! { #prefix::Overlay },
3742 })
3743 }
3744 }
3745
3746 impl ToTokens for LogicalPosition {
3747 fn to_tokens(&self, tokens: &mut TokenStream) {
3748 let LogicalPosition { x, y } = self;
3749 literal_struct!(tokens, ::tauri::utils::config::LogicalPosition, x, y)
3750 }
3751 }
3752
3753 impl ToTokens for crate::WindowEffect {
3754 fn to_tokens(&self, tokens: &mut TokenStream) {
3755 let prefix = quote! { ::tauri::utils::WindowEffect };
3756
3757 #[allow(deprecated)]
3758 tokens.append_all(match self {
3759 WindowEffect::AppearanceBased => quote! { #prefix::AppearanceBased},
3760 WindowEffect::Light => quote! { #prefix::Light},
3761 WindowEffect::Dark => quote! { #prefix::Dark},
3762 WindowEffect::MediumLight => quote! { #prefix::MediumLight},
3763 WindowEffect::UltraDark => quote! { #prefix::UltraDark},
3764 WindowEffect::Titlebar => quote! { #prefix::Titlebar},
3765 WindowEffect::Selection => quote! { #prefix::Selection},
3766 WindowEffect::Menu => quote! { #prefix::Menu},
3767 WindowEffect::Popover => quote! { #prefix::Popover},
3768 WindowEffect::Sidebar => quote! { #prefix::Sidebar},
3769 WindowEffect::HeaderView => quote! { #prefix::HeaderView},
3770 WindowEffect::Sheet => quote! { #prefix::Sheet},
3771 WindowEffect::WindowBackground => quote! { #prefix::WindowBackground},
3772 WindowEffect::HudWindow => quote! { #prefix::HudWindow},
3773 WindowEffect::FullScreenUI => quote! { #prefix::FullScreenUI},
3774 WindowEffect::Tooltip => quote! { #prefix::Tooltip},
3775 WindowEffect::ContentBackground => quote! { #prefix::ContentBackground},
3776 WindowEffect::UnderWindowBackground => quote! { #prefix::UnderWindowBackground},
3777 WindowEffect::UnderPageBackground => quote! { #prefix::UnderPageBackground},
3778 WindowEffect::Mica => quote! { #prefix::Mica},
3779 WindowEffect::MicaDark => quote! { #prefix::MicaDark},
3780 WindowEffect::MicaLight => quote! { #prefix::MicaLight},
3781 WindowEffect::Blur => quote! { #prefix::Blur},
3782 WindowEffect::Acrylic => quote! { #prefix::Acrylic},
3783 WindowEffect::Tabbed => quote! { #prefix::Tabbed },
3784 WindowEffect::TabbedDark => quote! { #prefix::TabbedDark },
3785 WindowEffect::TabbedLight => quote! { #prefix::TabbedLight },
3786 })
3787 }
3788 }
3789
3790 impl ToTokens for crate::WindowEffectState {
3791 fn to_tokens(&self, tokens: &mut TokenStream) {
3792 let prefix = quote! { ::tauri::utils::WindowEffectState };
3793
3794 #[allow(deprecated)]
3795 tokens.append_all(match self {
3796 WindowEffectState::Active => quote! { #prefix::Active},
3797 WindowEffectState::FollowsWindowActiveState => quote! { #prefix::FollowsWindowActiveState},
3798 WindowEffectState::Inactive => quote! { #prefix::Inactive},
3799 })
3800 }
3801 }
3802
3803 impl ToTokens for PreventOverflowMargin {
3804 fn to_tokens(&self, tokens: &mut TokenStream) {
3805 let width = self.width;
3806 let height = self.height;
3807
3808 literal_struct!(
3809 tokens,
3810 ::tauri::utils::config::PreventOverflowMargin,
3811 width,
3812 height
3813 )
3814 }
3815 }
3816
3817 impl ToTokens for PreventOverflowConfig {
3818 fn to_tokens(&self, tokens: &mut TokenStream) {
3819 let prefix = quote! { ::tauri::utils::config::PreventOverflowConfig };
3820
3821 #[allow(deprecated)]
3822 tokens.append_all(match self {
3823 Self::Enable(enable) => quote! { #prefix::Enable(#enable) },
3824 Self::Margin(margin) => quote! { #prefix::Margin(#margin) },
3825 })
3826 }
3827 }
3828
3829 impl ToTokens for ScrollBarStyle {
3830 fn to_tokens(&self, tokens: &mut TokenStream) {
3831 let prefix = quote! { ::tauri::utils::config::ScrollBarStyle };
3832
3833 tokens.append_all(match self {
3834 Self::Default => quote! { #prefix::Default },
3835 Self::FluentOverlay => quote! { #prefix::FluentOverlay },
3836 })
3837 }
3838 }
3839
3840 impl ToTokens for WindowConfig {
3841 fn to_tokens(&self, tokens: &mut TokenStream) {
3842 let label = str_lit(&self.label);
3843 let create = &self.create;
3844 let url = &self.url;
3845 let user_agent = opt_str_lit(self.user_agent.as_ref());
3846 let drag_drop_enabled = self.drag_drop_enabled;
3847 let center = self.center;
3848 let x = opt_lit(self.x.as_ref());
3849 let y = opt_lit(self.y.as_ref());
3850 let width = self.width;
3851 let height = self.height;
3852 let min_width = opt_lit(self.min_width.as_ref());
3853 let min_height = opt_lit(self.min_height.as_ref());
3854 let max_width = opt_lit(self.max_width.as_ref());
3855 let max_height = opt_lit(self.max_height.as_ref());
3856 let prevent_overflow = opt_lit(self.prevent_overflow.as_ref());
3857 let resizable = self.resizable;
3858 let maximizable = self.maximizable;
3859 let minimizable = self.minimizable;
3860 let closable = self.closable;
3861 let title = str_lit(&self.title);
3862 let proxy_url = opt_lit(self.proxy_url.as_ref().map(url_lit).as_ref());
3863 let fullscreen = self.fullscreen;
3864 let focus = self.focus;
3865 let focusable = self.focusable;
3866 let transparent = self.transparent;
3867 let maximized = self.maximized;
3868 let visible = self.visible;
3869 let decorations = self.decorations;
3870 let always_on_bottom = self.always_on_bottom;
3871 let always_on_top = self.always_on_top;
3872 let visible_on_all_workspaces = self.visible_on_all_workspaces;
3873 let content_protected = self.content_protected;
3874 let skip_taskbar = self.skip_taskbar;
3875 let window_classname = opt_str_lit(self.window_classname.as_ref());
3876 let theme = opt_lit(self.theme.as_ref());
3877 let title_bar_style = &self.title_bar_style;
3878 let traffic_light_position = opt_lit(self.traffic_light_position.as_ref());
3879 let hidden_title = self.hidden_title;
3880 let accept_first_mouse = self.accept_first_mouse;
3881 let tabbing_identifier = opt_str_lit(self.tabbing_identifier.as_ref());
3882 let additional_browser_args = opt_str_lit(self.additional_browser_args.as_ref());
3883 let shadow = self.shadow;
3884 let window_effects = opt_lit(self.window_effects.as_ref());
3885 let incognito = self.incognito;
3886 let parent = opt_str_lit(self.parent.as_ref());
3887 let zoom_hotkeys_enabled = self.zoom_hotkeys_enabled;
3888 let browser_extensions_enabled = self.browser_extensions_enabled;
3889 let use_https_scheme = self.use_https_scheme;
3890 let devtools = opt_lit(self.devtools.as_ref());
3891 let background_color = opt_lit(self.background_color.as_ref());
3892 let background_throttling = opt_lit(self.background_throttling.as_ref());
3893 let javascript_disabled = self.javascript_disabled;
3894 let allow_link_preview = self.allow_link_preview;
3895 let disable_input_accessory_view = self.disable_input_accessory_view;
3896 let data_directory = opt_lit(self.data_directory.as_ref().map(path_buf_lit).as_ref());
3897 let data_store_identifier = opt_vec_lit(self.data_store_identifier, identity);
3898 let scroll_bar_style = &self.scroll_bar_style;
3899 let activity_name = opt_lit(self.activity_name.as_ref());
3900 let created_by_activity_name = opt_lit(self.created_by_activity_name.as_ref());
3901 let requested_by_scene_identifier = opt_lit(self.requested_by_scene_identifier.as_ref());
3902 let general_autofill_enabled = self.general_autofill_enabled;
3903
3904 literal_struct!(
3905 tokens,
3906 ::tauri::utils::config::WindowConfig,
3907 label,
3908 url,
3909 create,
3910 user_agent,
3911 drag_drop_enabled,
3912 center,
3913 x,
3914 y,
3915 width,
3916 height,
3917 min_width,
3918 min_height,
3919 max_width,
3920 max_height,
3921 prevent_overflow,
3922 resizable,
3923 maximizable,
3924 minimizable,
3925 closable,
3926 title,
3927 proxy_url,
3928 fullscreen,
3929 focus,
3930 focusable,
3931 transparent,
3932 maximized,
3933 visible,
3934 decorations,
3935 always_on_bottom,
3936 always_on_top,
3937 visible_on_all_workspaces,
3938 content_protected,
3939 skip_taskbar,
3940 window_classname,
3941 theme,
3942 title_bar_style,
3943 traffic_light_position,
3944 hidden_title,
3945 accept_first_mouse,
3946 tabbing_identifier,
3947 additional_browser_args,
3948 shadow,
3949 window_effects,
3950 incognito,
3951 parent,
3952 zoom_hotkeys_enabled,
3953 browser_extensions_enabled,
3954 use_https_scheme,
3955 devtools,
3956 background_color,
3957 background_throttling,
3958 javascript_disabled,
3959 allow_link_preview,
3960 disable_input_accessory_view,
3961 data_directory,
3962 data_store_identifier,
3963 scroll_bar_style,
3964 activity_name,
3965 created_by_activity_name,
3966 requested_by_scene_identifier,
3967 general_autofill_enabled
3968 );
3969 }
3970 }
3971
3972 impl ToTokens for PatternKind {
3973 fn to_tokens(&self, tokens: &mut TokenStream) {
3974 let prefix = quote! { ::tauri::utils::config::PatternKind };
3975
3976 tokens.append_all(match self {
3977 Self::Brownfield => quote! { #prefix::Brownfield },
3978 #[cfg(not(feature = "isolation"))]
3979 Self::Isolation { dir: _ } => quote! { #prefix::Brownfield },
3980 #[cfg(feature = "isolation")]
3981 Self::Isolation { dir } => {
3982 let dir = path_buf_lit(dir);
3983 quote! { #prefix::Isolation { dir: #dir } }
3984 }
3985 })
3986 }
3987 }
3988
3989 impl ToTokens for WebviewInstallMode {
3990 fn to_tokens(&self, tokens: &mut TokenStream) {
3991 let prefix = quote! { ::tauri::utils::config::WebviewInstallMode };
3992
3993 tokens.append_all(match self {
3994 Self::Skip => quote! { #prefix::Skip },
3995 Self::DownloadBootstrapper { silent } => {
3996 quote! { #prefix::DownloadBootstrapper { silent: #silent } }
3997 }
3998 Self::EmbedBootstrapper { silent } => {
3999 quote! { #prefix::EmbedBootstrapper { silent: #silent } }
4000 }
4001 Self::OfflineInstaller { silent } => {
4002 quote! { #prefix::OfflineInstaller { silent: #silent } }
4003 }
4004 Self::FixedRuntime { path } => {
4005 let path = path_buf_lit(path);
4006 quote! { #prefix::FixedRuntime { path: #path } }
4007 }
4008 })
4009 }
4010 }
4011
4012 impl ToTokens for WindowsConfig {
4013 fn to_tokens(&self, tokens: &mut TokenStream) {
4014 let webview_install_mode = &self.webview_install_mode;
4015 tokens.append_all(quote! { ::tauri::utils::config::WindowsConfig {
4016 webview_install_mode: #webview_install_mode,
4017 ..Default::default()
4018 }})
4019 }
4020 }
4021
4022 impl ToTokens for BundleConfig {
4023 fn to_tokens(&self, tokens: &mut TokenStream) {
4024 let publisher = quote!(None);
4025 let homepage = quote!(None);
4026 let icon = vec_lit(&self.icon, str_lit);
4027 let active = self.active;
4028 let targets = quote!(Default::default());
4029 let create_updater_artifacts = quote!(Default::default());
4030 let resources = quote!(None);
4031 let copyright = quote!(None);
4032 let category = quote!(None);
4033 let file_associations = quote!(None);
4034 let short_description = quote!(None);
4035 let long_description = quote!(None);
4036 let use_local_tools_dir = self.use_local_tools_dir;
4037 let external_bin = opt_vec_lit(self.external_bin.as_ref(), str_lit);
4038 let windows = &self.windows;
4039 let license = opt_str_lit(self.license.as_ref());
4040 let license_file = opt_lit(self.license_file.as_ref().map(path_buf_lit).as_ref());
4041 let linux = quote!(Default::default());
4042 let macos = quote!(Default::default());
4043 let ios = quote!(Default::default());
4044 let android = quote!(Default::default());
4045
4046 literal_struct!(
4047 tokens,
4048 ::tauri::utils::config::BundleConfig,
4049 active,
4050 publisher,
4051 homepage,
4052 icon,
4053 targets,
4054 create_updater_artifacts,
4055 resources,
4056 copyright,
4057 category,
4058 license,
4059 license_file,
4060 file_associations,
4061 short_description,
4062 long_description,
4063 use_local_tools_dir,
4064 external_bin,
4065 windows,
4066 linux,
4067 macos,
4068 ios,
4069 android
4070 );
4071 }
4072 }
4073
4074 impl ToTokens for FrontendDist {
4075 fn to_tokens(&self, tokens: &mut TokenStream) {
4076 let prefix = quote! { ::tauri::utils::config::FrontendDist };
4077
4078 tokens.append_all(match self {
4079 Self::Url(url) => {
4080 let url = url_lit(url);
4081 quote! { #prefix::Url(#url) }
4082 }
4083 Self::Directory(path) => {
4084 let path = path_buf_lit(path);
4085 quote! { #prefix::Directory(#path) }
4086 }
4087 Self::Files(files) => {
4088 let files = vec_lit(files, path_buf_lit);
4089 quote! { #prefix::Files(#files) }
4090 }
4091 })
4092 }
4093 }
4094
4095 impl ToTokens for RunnerConfig {
4096 fn to_tokens(&self, tokens: &mut TokenStream) {
4097 let prefix = quote! { ::tauri::utils::config::RunnerConfig };
4098
4099 tokens.append_all(match self {
4100 Self::String(cmd) => {
4101 let cmd = cmd.as_str();
4102 quote!(#prefix::String(#cmd.into()))
4103 }
4104 Self::Object { cmd, cwd, args } => {
4105 let cmd = cmd.as_str();
4106 let cwd = opt_str_lit(cwd.as_ref());
4107 let args = opt_lit(args.as_ref().map(|v| vec_lit(v, str_lit)).as_ref());
4108 quote!(#prefix::Object {
4109 cmd: #cmd.into(),
4110 cwd: #cwd,
4111 args: #args,
4112 })
4113 }
4114 })
4115 }
4116 }
4117
4118 impl ToTokens for BuildConfig {
4119 fn to_tokens(&self, tokens: &mut TokenStream) {
4120 let dev_url = opt_lit(self.dev_url.as_ref().map(url_lit).as_ref());
4121 let frontend_dist = opt_lit(self.frontend_dist.as_ref());
4122 let runner = opt_lit(self.runner.as_ref());
4123 let before_dev_command = quote!(None);
4124 let before_build_command = quote!(None);
4125 let before_bundle_command = quote!(None);
4126 let features = quote!(None);
4127 let remove_unused_commands = quote!(false);
4128 let additional_watch_folders = quote!(Vec::new());
4129
4130 literal_struct!(
4131 tokens,
4132 ::tauri::utils::config::BuildConfig,
4133 runner,
4134 dev_url,
4135 frontend_dist,
4136 before_dev_command,
4137 before_build_command,
4138 before_bundle_command,
4139 features,
4140 remove_unused_commands,
4141 additional_watch_folders
4142 );
4143 }
4144 }
4145
4146 impl ToTokens for CspDirectiveSources {
4147 fn to_tokens(&self, tokens: &mut TokenStream) {
4148 let prefix = quote! { ::tauri::utils::config::CspDirectiveSources };
4149
4150 tokens.append_all(match self {
4151 Self::Inline(sources) => {
4152 let sources = sources.as_str();
4153 quote!(#prefix::Inline(#sources.into()))
4154 }
4155 Self::List(list) => {
4156 let list = vec_lit(list, str_lit);
4157 quote!(#prefix::List(#list))
4158 }
4159 })
4160 }
4161 }
4162
4163 impl ToTokens for Csp {
4164 fn to_tokens(&self, tokens: &mut TokenStream) {
4165 let prefix = quote! { ::tauri::utils::config::Csp };
4166
4167 tokens.append_all(match self {
4168 Self::Policy(policy) => {
4169 let policy = policy.as_str();
4170 quote!(#prefix::Policy(#policy.into()))
4171 }
4172 Self::DirectiveMap(list) => {
4173 let mut sorted: Vec<_> = list.iter().collect();
4177 sorted.sort_by_key(|(k, _)| *k);
4178 let map = map_lit(
4179 quote! { ::std::collections::HashMap },
4180 sorted,
4181 str_lit,
4182 identity,
4183 );
4184 quote!(#prefix::DirectiveMap(#map))
4185 }
4186 })
4187 }
4188 }
4189
4190 impl ToTokens for DisabledCspModificationKind {
4191 fn to_tokens(&self, tokens: &mut TokenStream) {
4192 let prefix = quote! { ::tauri::utils::config::DisabledCspModificationKind };
4193
4194 tokens.append_all(match self {
4195 Self::Flag(flag) => {
4196 quote! { #prefix::Flag(#flag) }
4197 }
4198 Self::List(directives) => {
4199 let directives = vec_lit(directives, str_lit);
4200 quote! { #prefix::List(#directives) }
4201 }
4202 });
4203 }
4204 }
4205
4206 impl ToTokens for CapabilityEntry {
4207 fn to_tokens(&self, tokens: &mut TokenStream) {
4208 let prefix = quote! { ::tauri::utils::config::CapabilityEntry };
4209
4210 tokens.append_all(match self {
4211 Self::Inlined(capability) => {
4212 quote! { #prefix::Inlined(#capability) }
4213 }
4214 Self::Reference(id) => {
4215 let id = str_lit(id);
4216 quote! { #prefix::Reference(#id) }
4217 }
4218 });
4219 }
4220 }
4221
4222 impl ToTokens for HeaderSource {
4223 fn to_tokens(&self, tokens: &mut TokenStream) {
4224 let prefix = quote! { ::tauri::utils::config::HeaderSource };
4225
4226 tokens.append_all(match self {
4227 Self::Inline(s) => {
4228 let line = s.as_str();
4229 quote!(#prefix::Inline(#line.into()))
4230 }
4231 Self::List(l) => {
4232 let list = vec_lit(l, str_lit);
4233 quote!(#prefix::List(#list))
4234 }
4235 Self::Map(m) => {
4236 let mut sorted: Vec<_> = m.iter().collect();
4240 sorted.sort_by_key(|(k, _)| *k);
4241 let map = map_lit(
4242 quote! { ::std::collections::HashMap },
4243 sorted,
4244 str_lit,
4245 str_lit,
4246 );
4247 quote!(#prefix::Map(#map))
4248 }
4249 })
4250 }
4251 }
4252
4253 impl ToTokens for HeaderConfig {
4254 fn to_tokens(&self, tokens: &mut TokenStream) {
4255 let access_control_allow_credentials =
4256 opt_lit(self.access_control_allow_credentials.as_ref());
4257 let access_control_allow_headers = opt_lit(self.access_control_allow_headers.as_ref());
4258 let access_control_allow_methods = opt_lit(self.access_control_allow_methods.as_ref());
4259 let access_control_expose_headers = opt_lit(self.access_control_expose_headers.as_ref());
4260 let access_control_max_age = opt_lit(self.access_control_max_age.as_ref());
4261 let cross_origin_embedder_policy = opt_lit(self.cross_origin_embedder_policy.as_ref());
4262 let cross_origin_opener_policy = opt_lit(self.cross_origin_opener_policy.as_ref());
4263 let cross_origin_resource_policy = opt_lit(self.cross_origin_resource_policy.as_ref());
4264 let permissions_policy = opt_lit(self.permissions_policy.as_ref());
4265 let service_worker_allowed = opt_lit(self.service_worker_allowed.as_ref());
4266 let timing_allow_origin = opt_lit(self.timing_allow_origin.as_ref());
4267 let x_content_type_options = opt_lit(self.x_content_type_options.as_ref());
4268 let tauri_custom_header = opt_lit(self.tauri_custom_header.as_ref());
4269
4270 literal_struct!(
4271 tokens,
4272 ::tauri::utils::config::HeaderConfig,
4273 access_control_allow_credentials,
4274 access_control_allow_headers,
4275 access_control_allow_methods,
4276 access_control_expose_headers,
4277 access_control_max_age,
4278 cross_origin_embedder_policy,
4279 cross_origin_opener_policy,
4280 cross_origin_resource_policy,
4281 permissions_policy,
4282 service_worker_allowed,
4283 timing_allow_origin,
4284 x_content_type_options,
4285 tauri_custom_header
4286 );
4287 }
4288 }
4289
4290 impl ToTokens for SecurityConfig {
4291 fn to_tokens(&self, tokens: &mut TokenStream) {
4292 let csp = opt_lit(self.csp.as_ref());
4293 let dev_csp = opt_lit(self.dev_csp.as_ref());
4294 let freeze_prototype = self.freeze_prototype;
4295 let dangerous_disable_asset_csp_modification = &self.dangerous_disable_asset_csp_modification;
4296 let asset_protocol = &self.asset_protocol;
4297 let pattern = &self.pattern;
4298 let capabilities = vec_lit(&self.capabilities, identity);
4299 let headers = opt_lit(self.headers.as_ref());
4300
4301 literal_struct!(
4302 tokens,
4303 ::tauri::utils::config::SecurityConfig,
4304 csp,
4305 dev_csp,
4306 freeze_prototype,
4307 dangerous_disable_asset_csp_modification,
4308 asset_protocol,
4309 pattern,
4310 capabilities,
4311 headers
4312 );
4313 }
4314 }
4315
4316 impl ToTokens for TrayIconConfig {
4317 fn to_tokens(&self, tokens: &mut TokenStream) {
4318 tokens.append_all(quote!(#[allow(deprecated)]));
4320
4321 let id = opt_str_lit(self.id.as_ref());
4322 let icon_as_template = self.icon_as_template;
4323 #[allow(deprecated)]
4324 let menu_on_left_click = self.menu_on_left_click;
4325 let show_menu_on_left_click = self.show_menu_on_left_click;
4326 let icon_path = path_buf_lit(&self.icon_path);
4327 let title = opt_str_lit(self.title.as_ref());
4328 let tooltip = opt_str_lit(self.tooltip.as_ref());
4329 literal_struct!(
4330 tokens,
4331 ::tauri::utils::config::TrayIconConfig,
4332 id,
4333 icon_path,
4334 icon_as_template,
4335 menu_on_left_click,
4336 show_menu_on_left_click,
4337 title,
4338 tooltip
4339 );
4340 }
4341 }
4342
4343 impl ToTokens for FsScope {
4344 fn to_tokens(&self, tokens: &mut TokenStream) {
4345 let prefix = quote! { ::tauri::utils::config::FsScope };
4346
4347 tokens.append_all(match self {
4348 Self::AllowedPaths(allow) => {
4349 let allowed_paths = vec_lit(allow, path_buf_lit);
4350 quote! { #prefix::AllowedPaths(#allowed_paths) }
4351 }
4352 Self::Scope { allow, deny , require_literal_leading_dot} => {
4353 let allow = vec_lit(allow, path_buf_lit);
4354 let deny = vec_lit(deny, path_buf_lit);
4355 let require_literal_leading_dot = opt_lit(require_literal_leading_dot.as_ref());
4356 quote! { #prefix::Scope { allow: #allow, deny: #deny, require_literal_leading_dot: #require_literal_leading_dot } }
4357 }
4358 });
4359 }
4360 }
4361
4362 impl ToTokens for AssetProtocolConfig {
4363 fn to_tokens(&self, tokens: &mut TokenStream) {
4364 let scope = &self.scope;
4365 tokens.append_all(quote! { ::tauri::utils::config::AssetProtocolConfig { scope: #scope, ..Default::default() } })
4366 }
4367 }
4368
4369 impl ToTokens for AppConfig {
4370 fn to_tokens(&self, tokens: &mut TokenStream) {
4371 let windows = vec_lit(&self.windows, identity);
4372 let security = &self.security;
4373 let tray_icon = opt_lit(self.tray_icon.as_ref());
4374 let macos_private_api = self.macos_private_api;
4375 let with_global_tauri = self.with_global_tauri;
4376 let enable_gtk_app_id = self.enable_gtk_app_id;
4377
4378 literal_struct!(
4379 tokens,
4380 ::tauri::utils::config::AppConfig,
4381 windows,
4382 security,
4383 tray_icon,
4384 macos_private_api,
4385 with_global_tauri,
4386 enable_gtk_app_id
4387 );
4388 }
4389 }
4390
4391 impl ToTokens for PluginConfig {
4392 fn to_tokens(&self, tokens: &mut TokenStream) {
4393 let mut sorted: Vec<_> = self.0.iter().collect();
4397 sorted.sort_by_key(|(k, _)| *k);
4398 let config = map_lit(
4399 quote! { ::std::collections::HashMap },
4400 sorted,
4401 str_lit,
4402 json_value_lit,
4403 );
4404 tokens.append_all(quote! { ::tauri::utils::config::PluginConfig(#config) })
4405 }
4406 }
4407
4408 impl ToTokens for Config {
4409 fn to_tokens(&self, tokens: &mut TokenStream) {
4410 let schema = quote!(None);
4411 let product_name = opt_str_lit(self.product_name.as_ref());
4412 let main_binary_name = opt_str_lit(self.main_binary_name.as_ref());
4413 let version = opt_str_lit(self.version.as_ref());
4414 let identifier = str_lit(&self.identifier);
4415 let app = &self.app;
4416 let build = &self.build;
4417 let bundle = &self.bundle;
4418 let plugins = &self.plugins;
4419
4420 literal_struct!(
4421 tokens,
4422 ::tauri::utils::config::Config,
4423 schema,
4424 product_name,
4425 main_binary_name,
4426 version,
4427 identifier,
4428 app,
4429 build,
4430 bundle,
4431 plugins
4432 );
4433 }
4434 }
4435}
4436
4437#[cfg(test)]
4438mod test {
4439 use super::*;
4440
4441 #[test]
4444 fn test_defaults() {
4446 let a_config = AppConfig::default();
4448 let b_config = BuildConfig::default();
4450 let d_windows: Vec<WindowConfig> = vec![];
4452 let d_bundle = BundleConfig::default();
4454
4455 let app = AppConfig {
4457 windows: vec![],
4458 security: SecurityConfig {
4459 csp: None,
4460 dev_csp: None,
4461 freeze_prototype: false,
4462 dangerous_disable_asset_csp_modification: DisabledCspModificationKind::Flag(false),
4463 asset_protocol: AssetProtocolConfig::default(),
4464 pattern: Default::default(),
4465 capabilities: Vec::new(),
4466 headers: None,
4467 },
4468 tray_icon: None,
4469 macos_private_api: false,
4470 with_global_tauri: false,
4471 enable_gtk_app_id: false,
4472 };
4473
4474 let build = BuildConfig {
4476 runner: None,
4477 dev_url: None,
4478 frontend_dist: None,
4479 before_dev_command: None,
4480 before_build_command: None,
4481 before_bundle_command: None,
4482 features: None,
4483 remove_unused_commands: false,
4484 additional_watch_folders: Vec::new(),
4485 };
4486
4487 let bundle = BundleConfig {
4489 active: false,
4490 targets: Default::default(),
4491 create_updater_artifacts: Default::default(),
4492 publisher: None,
4493 homepage: None,
4494 icon: Vec::new(),
4495 resources: None,
4496 copyright: None,
4497 category: None,
4498 file_associations: None,
4499 short_description: None,
4500 long_description: None,
4501 use_local_tools_dir: false,
4502 license: None,
4503 license_file: None,
4504 linux: Default::default(),
4505 macos: Default::default(),
4506 external_bin: None,
4507 windows: Default::default(),
4508 ios: Default::default(),
4509 android: Default::default(),
4510 };
4511
4512 assert_eq!(a_config, app);
4514 assert_eq!(b_config, build);
4515 assert_eq!(d_bundle, bundle);
4516 assert_eq!(d_windows, app.windows);
4517 }
4518
4519 #[test]
4520 fn parse_hex_color() {
4521 use super::Color;
4522
4523 assert_eq!(Color(255, 255, 255, 255), "fff".parse().unwrap());
4524 assert_eq!(Color(255, 255, 255, 255), "#fff".parse().unwrap());
4525 assert_eq!(Color(0, 0, 0, 255), "#000000".parse().unwrap());
4526 assert_eq!(Color(0, 0, 0, 255), "#000000ff".parse().unwrap());
4527 assert_eq!(Color(0, 255, 0, 255), "#00ff00ff".parse().unwrap());
4528 }
4529
4530 #[test]
4531 fn test_runner_config_string_format() {
4532 use super::RunnerConfig;
4533
4534 let json = r#""cargo""#;
4536 let runner: RunnerConfig = serde_json::from_str(json).unwrap();
4537
4538 assert_eq!(runner.cmd(), "cargo");
4539 assert_eq!(runner.cwd(), None);
4540 assert_eq!(runner.args(), None);
4541
4542 let serialized = serde_json::to_string(&runner).unwrap();
4544 assert_eq!(serialized, r#""cargo""#);
4545 }
4546
4547 #[test]
4548 fn test_runner_config_object_format_full() {
4549 use super::RunnerConfig;
4550
4551 let json = r#"{"cmd": "my_runner", "cwd": "/tmp/build", "args": ["--quiet", "--verbose"]}"#;
4553 let runner: RunnerConfig = serde_json::from_str(json).unwrap();
4554
4555 assert_eq!(runner.cmd(), "my_runner");
4556 assert_eq!(runner.cwd(), Some("/tmp/build"));
4557 assert_eq!(
4558 runner.args(),
4559 Some(&["--quiet".to_string(), "--verbose".to_string()][..])
4560 );
4561
4562 let serialized = serde_json::to_string(&runner).unwrap();
4564 let deserialized: RunnerConfig = serde_json::from_str(&serialized).unwrap();
4565 assert_eq!(runner, deserialized);
4566 }
4567
4568 #[test]
4569 fn test_runner_config_object_format_minimal() {
4570 use super::RunnerConfig;
4571
4572 let json = r#"{"cmd": "cross"}"#;
4574 let runner: RunnerConfig = serde_json::from_str(json).unwrap();
4575
4576 assert_eq!(runner.cmd(), "cross");
4577 assert_eq!(runner.cwd(), None);
4578 assert_eq!(runner.args(), None);
4579 }
4580
4581 #[test]
4582 fn test_runner_config_default() {
4583 use super::RunnerConfig;
4584
4585 let default_runner = RunnerConfig::default();
4586 assert_eq!(default_runner.cmd(), "cargo");
4587 assert_eq!(default_runner.cwd(), None);
4588 assert_eq!(default_runner.args(), None);
4589 }
4590
4591 #[test]
4592 fn test_runner_config_from_str() {
4593 use super::RunnerConfig;
4594
4595 let runner: RunnerConfig = "my_runner".into();
4597 assert_eq!(runner.cmd(), "my_runner");
4598 assert_eq!(runner.cwd(), None);
4599 assert_eq!(runner.args(), None);
4600 }
4601
4602 #[test]
4603 fn test_runner_config_from_string() {
4604 use super::RunnerConfig;
4605
4606 let runner: RunnerConfig = "another_runner".to_string().into();
4608 assert_eq!(runner.cmd(), "another_runner");
4609 assert_eq!(runner.cwd(), None);
4610 assert_eq!(runner.args(), None);
4611 }
4612
4613 #[test]
4614 fn test_runner_config_from_str_parse() {
4615 use super::RunnerConfig;
4616 use std::str::FromStr;
4617
4618 let runner = RunnerConfig::from_str("parsed_runner").unwrap();
4620 assert_eq!(runner.cmd(), "parsed_runner");
4621 assert_eq!(runner.cwd(), None);
4622 assert_eq!(runner.args(), None);
4623 }
4624
4625 #[test]
4626 fn test_runner_config_in_build_config() {
4627 use super::BuildConfig;
4628
4629 let json = r#"{"runner": "cargo"}"#;
4631 let build_config: BuildConfig = serde_json::from_str(json).unwrap();
4632
4633 let runner = build_config.runner.unwrap();
4634 assert_eq!(runner.cmd(), "cargo");
4635 assert_eq!(runner.cwd(), None);
4636 assert_eq!(runner.args(), None);
4637 }
4638
4639 #[test]
4640 fn test_runner_config_in_build_config_object() {
4641 use super::BuildConfig;
4642
4643 let json = r#"{"runner": {"cmd": "cross", "cwd": "/workspace", "args": ["--target", "x86_64-unknown-linux-gnu"]}}"#;
4645 let build_config: BuildConfig = serde_json::from_str(json).unwrap();
4646
4647 let runner = build_config.runner.unwrap();
4648 assert_eq!(runner.cmd(), "cross");
4649 assert_eq!(runner.cwd(), Some("/workspace"));
4650 assert_eq!(
4651 runner.args(),
4652 Some(
4653 &[
4654 "--target".to_string(),
4655 "x86_64-unknown-linux-gnu".to_string()
4656 ][..]
4657 )
4658 );
4659 }
4660
4661 #[test]
4662 fn test_runner_config_in_full_config() {
4663 use super::Config;
4664
4665 let json = r#"{
4667 "productName": "Test App",
4668 "version": "1.0.0",
4669 "identifier": "com.test.app",
4670 "build": {
4671 "runner": {
4672 "cmd": "my_custom_cargo",
4673 "cwd": "/tmp/build",
4674 "args": ["--quiet", "--verbose"]
4675 }
4676 }
4677 }"#;
4678
4679 let config: Config = serde_json::from_str(json).unwrap();
4680 let runner = config.build.runner.unwrap();
4681
4682 assert_eq!(runner.cmd(), "my_custom_cargo");
4683 assert_eq!(runner.cwd(), Some("/tmp/build"));
4684 assert_eq!(
4685 runner.args(),
4686 Some(&["--quiet".to_string(), "--verbose".to_string()][..])
4687 );
4688 }
4689
4690 #[test]
4691 fn test_runner_config_equality() {
4692 use super::RunnerConfig;
4693
4694 let runner1 = RunnerConfig::String("cargo".to_string());
4695 let runner2 = RunnerConfig::String("cargo".to_string());
4696 let runner3 = RunnerConfig::String("cross".to_string());
4697
4698 assert_eq!(runner1, runner2);
4699 assert_ne!(runner1, runner3);
4700
4701 let runner4 = RunnerConfig::Object {
4702 cmd: "cargo".to_string(),
4703 cwd: Some("/tmp".to_string()),
4704 args: Some(vec!["--quiet".to_string()]),
4705 };
4706 let runner5 = RunnerConfig::Object {
4707 cmd: "cargo".to_string(),
4708 cwd: Some("/tmp".to_string()),
4709 args: Some(vec!["--quiet".to_string()]),
4710 };
4711
4712 assert_eq!(runner4, runner5);
4713 assert_ne!(runner1, runner4);
4714 }
4715
4716 #[test]
4717 fn test_runner_config_untagged_serialization() {
4718 use super::RunnerConfig;
4719
4720 let string_runner = RunnerConfig::String("cargo".to_string());
4722 let string_json = serde_json::to_string(&string_runner).unwrap();
4723 assert_eq!(string_json, r#""cargo""#);
4724
4725 let object_runner = RunnerConfig::Object {
4727 cmd: "cross".to_string(),
4728 cwd: None,
4729 args: None,
4730 };
4731 let object_json = serde_json::to_string(&object_runner).unwrap();
4732 assert!(object_json.contains("\"cmd\":\"cross\""));
4733 assert!(object_json.contains("\"cwd\":null") || !object_json.contains("cwd"));
4735 assert!(object_json.contains("\"args\":null") || !object_json.contains("args"));
4736 }
4737
4738 #[test]
4739 fn window_config_default_same_as_deserialize() {
4740 let config_from_deserialization: WindowConfig = serde_json::from_str("{}").unwrap();
4741 let config_from_default: WindowConfig = WindowConfig::default();
4742
4743 assert_eq!(config_from_deserialization, config_from_default);
4744 }
4745}