1use http::response::Builder;
27#[cfg(feature = "schema")]
28use schemars::JsonSchema;
29use semver::Version;
30use serde::{
31 de::{Deserializer, Error as DeError, Visitor},
32 Deserialize, Serialize, Serializer,
33};
34use serde_json::Value as JsonValue;
35use serde_untagged::UntaggedEnumVisitor;
36use serde_with::skip_serializing_none;
37use url::Url;
38
39use std::{
40 collections::HashMap,
41 fmt::{self, Display},
42 fs::read_to_string,
43 path::PathBuf,
44 str::FromStr,
45};
46
47pub mod parse;
49
50use crate::{acl::capability::Capability, TitleBarStyle, WindowEffect, WindowEffectState};
51
52pub use self::parse::parse;
53
54fn default_true() -> bool {
55 true
56}
57
58#[derive(PartialEq, Eq, Debug, Clone, Serialize)]
60#[cfg_attr(feature = "schema", derive(JsonSchema))]
61#[serde(untagged)]
62#[non_exhaustive]
63pub enum WebviewUrl {
64 External(Url),
66 App(PathBuf),
70 CustomProtocol(Url),
72}
73
74impl<'de> Deserialize<'de> for WebviewUrl {
75 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
76 where
77 D: Deserializer<'de>,
78 {
79 #[derive(Deserialize)]
80 #[serde(untagged)]
81 enum WebviewUrlDeserializer {
82 Url(Url),
83 Path(PathBuf),
84 }
85
86 match WebviewUrlDeserializer::deserialize(deserializer)? {
87 WebviewUrlDeserializer::Url(u) => {
88 if u.scheme() == "https" || u.scheme() == "http" {
89 Ok(Self::External(u))
90 } else {
91 Ok(Self::CustomProtocol(u))
92 }
93 }
94 WebviewUrlDeserializer::Path(p) => Ok(Self::App(p)),
95 }
96 }
97}
98
99impl fmt::Display for WebviewUrl {
100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101 match self {
102 Self::External(url) | Self::CustomProtocol(url) => write!(f, "{url}"),
103 Self::App(path) => write!(f, "{}", path.display()),
104 }
105 }
106}
107
108impl Default for WebviewUrl {
109 fn default() -> Self {
110 Self::App("index.html".into())
111 }
112}
113
114#[derive(Debug, PartialEq, Eq, Clone)]
116#[cfg_attr(feature = "schema", derive(JsonSchema))]
117#[cfg_attr(feature = "schema", schemars(rename_all = "lowercase"))]
118pub enum BundleType {
119 Deb,
121 Rpm,
123 AppImage,
125 Msi,
127 Nsis,
129 App,
131 Dmg,
133}
134
135impl BundleType {
136 fn all() -> &'static [Self] {
138 &[
139 BundleType::Deb,
140 BundleType::Rpm,
141 BundleType::AppImage,
142 BundleType::Msi,
143 BundleType::Nsis,
144 BundleType::App,
145 BundleType::Dmg,
146 ]
147 }
148}
149
150impl Display for BundleType {
151 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152 write!(
153 f,
154 "{}",
155 match self {
156 Self::Deb => "deb",
157 Self::Rpm => "rpm",
158 Self::AppImage => "appimage",
159 Self::Msi => "msi",
160 Self::Nsis => "nsis",
161 Self::App => "app",
162 Self::Dmg => "dmg",
163 }
164 )
165 }
166}
167
168impl Serialize for BundleType {
169 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
170 where
171 S: Serializer,
172 {
173 serializer.serialize_str(self.to_string().as_ref())
174 }
175}
176
177impl<'de> Deserialize<'de> for BundleType {
178 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
179 where
180 D: Deserializer<'de>,
181 {
182 let s = String::deserialize(deserializer)?;
183 match s.to_lowercase().as_str() {
184 "deb" => Ok(Self::Deb),
185 "rpm" => Ok(Self::Rpm),
186 "appimage" => Ok(Self::AppImage),
187 "msi" => Ok(Self::Msi),
188 "nsis" => Ok(Self::Nsis),
189 "app" => Ok(Self::App),
190 "dmg" => Ok(Self::Dmg),
191 _ => Err(DeError::custom(format!("unknown bundle target '{s}'"))),
192 }
193 }
194}
195
196#[derive(Debug, PartialEq, Eq, Clone)]
198pub enum BundleTarget {
199 All,
201 List(Vec<BundleType>),
203 One(BundleType),
205}
206
207#[cfg(feature = "schema")]
208impl schemars::JsonSchema for BundleTarget {
209 fn schema_name() -> std::string::String {
210 "BundleTarget".to_owned()
211 }
212
213 fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
214 let any_of = vec![
215 schemars::schema::SchemaObject {
216 const_value: Some("all".into()),
217 metadata: Some(Box::new(schemars::schema::Metadata {
218 description: Some("Bundle all targets.".to_owned()),
219 ..Default::default()
220 })),
221 ..Default::default()
222 }
223 .into(),
224 schemars::_private::metadata::add_description(
225 gen.subschema_for::<Vec<BundleType>>(),
226 "A list of bundle targets.",
227 ),
228 schemars::_private::metadata::add_description(
229 gen.subschema_for::<BundleType>(),
230 "A single bundle target.",
231 ),
232 ];
233
234 schemars::schema::SchemaObject {
235 subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
236 any_of: Some(any_of),
237 ..Default::default()
238 })),
239 metadata: Some(Box::new(schemars::schema::Metadata {
240 description: Some("Targets to bundle. Each value is case insensitive.".to_owned()),
241 ..Default::default()
242 })),
243 ..Default::default()
244 }
245 .into()
246 }
247}
248
249impl Default for BundleTarget {
250 fn default() -> Self {
251 Self::All
252 }
253}
254
255impl Serialize for BundleTarget {
256 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
257 where
258 S: Serializer,
259 {
260 match self {
261 Self::All => serializer.serialize_str("all"),
262 Self::List(l) => l.serialize(serializer),
263 Self::One(t) => serializer.serialize_str(t.to_string().as_ref()),
264 }
265 }
266}
267
268impl<'de> Deserialize<'de> for BundleTarget {
269 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
270 where
271 D: Deserializer<'de>,
272 {
273 #[derive(Deserialize, Serialize)]
274 #[serde(untagged)]
275 pub enum BundleTargetInner {
276 List(Vec<BundleType>),
277 One(BundleType),
278 All(String),
279 }
280
281 match BundleTargetInner::deserialize(deserializer)? {
282 BundleTargetInner::All(s) if s.to_lowercase() == "all" => Ok(Self::All),
283 BundleTargetInner::All(t) => Err(DeError::custom(format!(
284 "invalid bundle type {t}, expected one of `all`, {}",
285 BundleType::all()
286 .iter()
287 .map(|b| format!("`{b}`"))
288 .collect::<Vec<_>>()
289 .join(", ")
290 ))),
291 BundleTargetInner::List(l) => Ok(Self::List(l)),
292 BundleTargetInner::One(t) => Ok(Self::One(t)),
293 }
294 }
295}
296
297impl BundleTarget {
298 #[allow(dead_code)]
300 pub fn to_vec(&self) -> Vec<BundleType> {
301 match self {
302 Self::All => BundleType::all().to_vec(),
303 Self::List(list) => list.clone(),
304 Self::One(i) => vec![i.clone()],
305 }
306 }
307}
308
309#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
313#[cfg_attr(feature = "schema", derive(JsonSchema))]
314#[serde(rename_all = "camelCase", deny_unknown_fields)]
315pub struct AppImageConfig {
316 #[serde(default, alias = "bundle-media-framework")]
319 pub bundle_media_framework: bool,
320 #[serde(default)]
322 pub files: HashMap<PathBuf, PathBuf>,
323}
324
325#[skip_serializing_none]
329#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
330#[cfg_attr(feature = "schema", derive(JsonSchema))]
331#[serde(rename_all = "camelCase", deny_unknown_fields)]
332pub struct DebConfig {
333 pub depends: Option<Vec<String>>,
335 pub recommends: Option<Vec<String>>,
337 pub provides: Option<Vec<String>>,
339 pub conflicts: Option<Vec<String>>,
341 pub replaces: Option<Vec<String>>,
343 #[serde(default)]
345 pub files: HashMap<PathBuf, PathBuf>,
346 pub section: Option<String>,
348 pub priority: Option<String>,
351 pub changelog: Option<PathBuf>,
354 #[serde(alias = "desktop-template")]
358 pub desktop_template: Option<PathBuf>,
359 #[serde(alias = "pre-install-script")]
362 pub pre_install_script: Option<PathBuf>,
363 #[serde(alias = "post-install-script")]
366 pub post_install_script: Option<PathBuf>,
367 #[serde(alias = "pre-remove-script")]
370 pub pre_remove_script: Option<PathBuf>,
371 #[serde(alias = "post-remove-script")]
374 pub post_remove_script: Option<PathBuf>,
375}
376
377#[skip_serializing_none]
381#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
382#[cfg_attr(feature = "schema", derive(JsonSchema))]
383#[serde(rename_all = "camelCase", deny_unknown_fields)]
384pub struct LinuxConfig {
385 #[serde(default)]
387 pub appimage: AppImageConfig,
388 #[serde(default)]
390 pub deb: DebConfig,
391 #[serde(default)]
393 pub rpm: RpmConfig,
394}
395
396#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
398#[cfg_attr(feature = "schema", derive(JsonSchema))]
399#[serde(rename_all = "camelCase", deny_unknown_fields, tag = "type")]
400#[non_exhaustive]
401pub enum RpmCompression {
402 Gzip {
404 level: u32,
406 },
407 Zstd {
409 level: i32,
411 },
412 Xz {
414 level: u32,
416 },
417 Bzip2 {
419 level: u32,
421 },
422 None,
424}
425
426#[skip_serializing_none]
428#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
429#[cfg_attr(feature = "schema", derive(JsonSchema))]
430#[serde(rename_all = "camelCase", deny_unknown_fields)]
431pub struct RpmConfig {
432 pub depends: Option<Vec<String>>,
434 pub recommends: Option<Vec<String>>,
436 pub provides: Option<Vec<String>>,
438 pub conflicts: Option<Vec<String>>,
441 pub obsoletes: Option<Vec<String>>,
444 #[serde(default = "default_release")]
446 pub release: String,
447 #[serde(default)]
449 pub epoch: u32,
450 #[serde(default)]
452 pub files: HashMap<PathBuf, PathBuf>,
453 #[serde(alias = "desktop-template")]
457 pub desktop_template: Option<PathBuf>,
458 #[serde(alias = "pre-install-script")]
461 pub pre_install_script: Option<PathBuf>,
462 #[serde(alias = "post-install-script")]
465 pub post_install_script: Option<PathBuf>,
466 #[serde(alias = "pre-remove-script")]
469 pub pre_remove_script: Option<PathBuf>,
470 #[serde(alias = "post-remove-script")]
473 pub post_remove_script: Option<PathBuf>,
474 pub compression: Option<RpmCompression>,
476}
477
478impl Default for RpmConfig {
479 fn default() -> Self {
480 Self {
481 depends: None,
482 recommends: None,
483 provides: None,
484 conflicts: None,
485 obsoletes: None,
486 release: default_release(),
487 epoch: 0,
488 files: Default::default(),
489 desktop_template: None,
490 pre_install_script: None,
491 post_install_script: None,
492 pre_remove_script: None,
493 post_remove_script: None,
494 compression: None,
495 }
496 }
497}
498
499fn default_release() -> String {
500 "1".into()
501}
502
503#[derive(Default, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
505#[cfg_attr(feature = "schema", derive(JsonSchema))]
506#[serde(rename_all = "camelCase", deny_unknown_fields)]
507pub struct Position {
508 pub x: u32,
510 pub y: u32,
512}
513
514#[derive(Default, Debug, PartialEq, Clone, Deserialize, Serialize)]
516#[cfg_attr(feature = "schema", derive(JsonSchema))]
517#[serde(rename_all = "camelCase", deny_unknown_fields)]
518pub struct LogicalPosition {
519 pub x: f64,
521 pub y: f64,
523}
524
525#[derive(Default, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
527#[cfg_attr(feature = "schema", derive(JsonSchema))]
528#[serde(rename_all = "camelCase", deny_unknown_fields)]
529pub struct Size {
530 pub width: u32,
532 pub height: u32,
534}
535
536#[skip_serializing_none]
540#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
541#[cfg_attr(feature = "schema", derive(JsonSchema))]
542#[serde(rename_all = "camelCase", deny_unknown_fields)]
543pub struct DmgConfig {
544 pub background: Option<PathBuf>,
546 pub window_position: Option<Position>,
548 #[serde(default = "dmg_window_size", alias = "window-size")]
550 pub window_size: Size,
551 #[serde(default = "dmg_app_position", alias = "app-position")]
553 pub app_position: Position,
554 #[serde(
556 default = "dmg_application_folder_position",
557 alias = "application-folder-position"
558 )]
559 pub application_folder_position: Position,
560}
561
562impl Default for DmgConfig {
563 fn default() -> Self {
564 Self {
565 background: None,
566 window_position: None,
567 window_size: dmg_window_size(),
568 app_position: dmg_app_position(),
569 application_folder_position: dmg_application_folder_position(),
570 }
571 }
572}
573
574fn dmg_window_size() -> Size {
575 Size {
576 width: 660,
577 height: 400,
578 }
579}
580
581fn dmg_app_position() -> Position {
582 Position { x: 180, y: 170 }
583}
584
585fn dmg_application_folder_position() -> Position {
586 Position { x: 480, y: 170 }
587}
588
589fn de_macos_minimum_system_version<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
590where
591 D: Deserializer<'de>,
592{
593 let version = Option::<String>::deserialize(deserializer)?;
594 match version {
595 Some(v) if v.is_empty() => Ok(macos_minimum_system_version()),
596 e => Ok(e),
597 }
598}
599
600#[skip_serializing_none]
604#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
605#[cfg_attr(feature = "schema", derive(JsonSchema))]
606#[serde(rename_all = "camelCase", deny_unknown_fields)]
607pub struct MacConfig {
608 pub frameworks: Option<Vec<String>>,
612 #[serde(default)]
614 pub files: HashMap<PathBuf, PathBuf>,
615 #[serde(alias = "bundle-version")]
619 pub bundle_version: Option<String>,
620 #[serde(
627 deserialize_with = "de_macos_minimum_system_version",
628 default = "macos_minimum_system_version",
629 alias = "minimum-system-version"
630 )]
631 pub minimum_system_version: Option<String>,
632 #[serde(alias = "exception-domain")]
635 pub exception_domain: Option<String>,
636 #[serde(alias = "signing-identity")]
638 pub signing_identity: Option<String>,
639 #[serde(alias = "hardened-runtime", default = "default_true")]
643 pub hardened_runtime: bool,
644 #[serde(alias = "provider-short-name")]
646 pub provider_short_name: Option<String>,
647 pub entitlements: Option<String>,
649 #[serde(default)]
651 pub dmg: DmgConfig,
652}
653
654impl Default for MacConfig {
655 fn default() -> Self {
656 Self {
657 frameworks: None,
658 files: HashMap::new(),
659 bundle_version: None,
660 minimum_system_version: macos_minimum_system_version(),
661 exception_domain: None,
662 signing_identity: None,
663 hardened_runtime: true,
664 provider_short_name: None,
665 entitlements: None,
666 dmg: Default::default(),
667 }
668 }
669}
670
671fn macos_minimum_system_version() -> Option<String> {
672 Some("10.13".into())
673}
674
675fn ios_minimum_system_version() -> String {
676 "13.0".into()
677}
678
679#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
683#[cfg_attr(feature = "schema", derive(JsonSchema))]
684#[serde(rename_all = "camelCase", deny_unknown_fields)]
685pub struct WixLanguageConfig {
686 #[serde(alias = "locale-path")]
688 pub locale_path: Option<String>,
689}
690
691#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
693#[cfg_attr(feature = "schema", derive(JsonSchema))]
694#[serde(untagged)]
695pub enum WixLanguage {
696 One(String),
698 List(Vec<String>),
700 Localized(HashMap<String, WixLanguageConfig>),
702}
703
704impl Default for WixLanguage {
705 fn default() -> Self {
706 Self::One("en-US".into())
707 }
708}
709
710#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
714#[cfg_attr(feature = "schema", derive(JsonSchema))]
715#[serde(rename_all = "camelCase", deny_unknown_fields)]
716pub struct WixConfig {
717 pub version: Option<String>,
726 #[serde(alias = "upgrade-code")]
735 pub upgrade_code: Option<uuid::Uuid>,
736 #[serde(default)]
738 pub language: WixLanguage,
739 pub template: Option<PathBuf>,
741 #[serde(default, alias = "fragment-paths")]
743 pub fragment_paths: Vec<PathBuf>,
744 #[serde(default, alias = "component-group-refs")]
746 pub component_group_refs: Vec<String>,
747 #[serde(default, alias = "component-refs")]
749 pub component_refs: Vec<String>,
750 #[serde(default, alias = "feature-group-refs")]
752 pub feature_group_refs: Vec<String>,
753 #[serde(default, alias = "feature-refs")]
755 pub feature_refs: Vec<String>,
756 #[serde(default, alias = "merge-refs")]
758 pub merge_refs: Vec<String>,
759 #[serde(default, alias = "enable-elevated-update-task")]
761 pub enable_elevated_update_task: bool,
762 #[serde(alias = "banner-path")]
767 pub banner_path: Option<PathBuf>,
768 #[serde(alias = "dialog-image-path")]
773 pub dialog_image_path: Option<PathBuf>,
774}
775
776#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
780#[cfg_attr(feature = "schema", derive(JsonSchema))]
781#[serde(rename_all = "camelCase", deny_unknown_fields)]
782pub enum NsisCompression {
783 Zlib,
785 Bzip2,
787 Lzma,
789 None,
791}
792
793impl Default for NsisCompression {
794 fn default() -> Self {
795 Self::Lzma
796 }
797}
798
799#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
801#[serde(rename_all = "camelCase", deny_unknown_fields)]
802#[cfg_attr(feature = "schema", derive(JsonSchema))]
803pub enum NSISInstallerMode {
804 CurrentUser,
810 PerMachine,
815 Both,
821}
822
823impl Default for NSISInstallerMode {
824 fn default() -> Self {
825 Self::CurrentUser
826 }
827}
828
829#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
831#[cfg_attr(feature = "schema", derive(JsonSchema))]
832#[serde(rename_all = "camelCase", deny_unknown_fields)]
833pub struct NsisConfig {
834 pub template: Option<PathBuf>,
836 #[serde(alias = "header-image")]
840 pub header_image: Option<PathBuf>,
841 #[serde(alias = "sidebar-image")]
845 pub sidebar_image: Option<PathBuf>,
846 #[serde(alias = "install-icon")]
848 pub installer_icon: Option<PathBuf>,
849 #[serde(default, alias = "install-mode")]
851 pub install_mode: NSISInstallerMode,
852 pub languages: Option<Vec<String>>,
858 pub custom_language_files: Option<HashMap<String, PathBuf>>,
865 #[serde(default, alias = "display-language-selector")]
868 pub display_language_selector: bool,
869 #[serde(default)]
873 pub compression: NsisCompression,
874 #[serde(alias = "start-menu-folder")]
883 pub start_menu_folder: Option<String>,
884 #[serde(alias = "installer-hooks")]
915 pub installer_hooks: Option<PathBuf>,
916 #[serde(alias = "minimum-webview2-version")]
920 pub minimum_webview2_version: Option<String>,
921}
922
923#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
928#[serde(tag = "type", rename_all = "camelCase", deny_unknown_fields)]
929#[cfg_attr(feature = "schema", derive(JsonSchema))]
930pub enum WebviewInstallMode {
931 Skip,
933 DownloadBootstrapper {
937 #[serde(default = "default_true")]
939 silent: bool,
940 },
941 EmbedBootstrapper {
945 #[serde(default = "default_true")]
947 silent: bool,
948 },
949 OfflineInstaller {
953 #[serde(default = "default_true")]
955 silent: bool,
956 },
957 FixedRuntime {
960 path: PathBuf,
965 },
966}
967
968impl Default for WebviewInstallMode {
969 fn default() -> Self {
970 Self::DownloadBootstrapper { silent: true }
971 }
972}
973
974#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
976#[cfg_attr(feature = "schema", derive(JsonSchema))]
977#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
978pub enum CustomSignCommandConfig {
979 Command(String),
988 CommandWithOptions {
993 cmd: String,
995 args: Vec<String>,
999 },
1000}
1001
1002#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1006#[cfg_attr(feature = "schema", derive(JsonSchema))]
1007#[serde(rename_all = "camelCase", deny_unknown_fields)]
1008pub struct WindowsConfig {
1009 #[serde(alias = "digest-algorithm")]
1012 pub digest_algorithm: Option<String>,
1013 #[serde(alias = "certificate-thumbprint")]
1015 pub certificate_thumbprint: Option<String>,
1016 #[serde(alias = "timestamp-url")]
1018 pub timestamp_url: Option<String>,
1019 #[serde(default)]
1022 pub tsp: bool,
1023 #[serde(default, alias = "webview-install-mode")]
1025 pub webview_install_mode: WebviewInstallMode,
1026 #[serde(default = "default_true", alias = "allow-downgrades")]
1032 pub allow_downgrades: bool,
1033 pub wix: Option<WixConfig>,
1035 pub nsis: Option<NsisConfig>,
1037 #[serde(alias = "sign-command")]
1045 pub sign_command: Option<CustomSignCommandConfig>,
1046}
1047
1048impl Default for WindowsConfig {
1049 fn default() -> Self {
1050 Self {
1051 digest_algorithm: None,
1052 certificate_thumbprint: None,
1053 timestamp_url: None,
1054 tsp: false,
1055 webview_install_mode: Default::default(),
1056 allow_downgrades: true,
1057 wix: None,
1058 nsis: None,
1059 sign_command: None,
1060 }
1061 }
1062}
1063
1064#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1066#[cfg_attr(feature = "schema", derive(JsonSchema))]
1067pub enum BundleTypeRole {
1068 #[default]
1070 Editor,
1071 Viewer,
1073 Shell,
1075 QLGenerator,
1077 None,
1079}
1080
1081impl Display for BundleTypeRole {
1082 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1083 match self {
1084 Self::Editor => write!(f, "Editor"),
1085 Self::Viewer => write!(f, "Viewer"),
1086 Self::Shell => write!(f, "Shell"),
1087 Self::QLGenerator => write!(f, "QLGenerator"),
1088 Self::None => write!(f, "None"),
1089 }
1090 }
1091}
1092
1093#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1097#[cfg_attr(feature = "schema", derive(JsonSchema))]
1098pub enum HandlerRank {
1099 #[default]
1101 Default,
1102 Owner,
1104 Alternate,
1106 None,
1108}
1109
1110impl Display for HandlerRank {
1111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1112 match self {
1113 Self::Default => write!(f, "Default"),
1114 Self::Owner => write!(f, "Owner"),
1115 Self::Alternate => write!(f, "Alternate"),
1116 Self::None => write!(f, "None"),
1117 }
1118 }
1119}
1120
1121#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
1125#[cfg_attr(feature = "schema", derive(JsonSchema))]
1126pub struct AssociationExt(pub String);
1127
1128impl fmt::Display for AssociationExt {
1129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1130 write!(f, "{}", self.0)
1131 }
1132}
1133
1134impl<'d> serde::Deserialize<'d> for AssociationExt {
1135 fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {
1136 let ext = String::deserialize(deserializer)?;
1137 if let Some(ext) = ext.strip_prefix('.') {
1138 Ok(AssociationExt(ext.into()))
1139 } else {
1140 Ok(AssociationExt(ext))
1141 }
1142 }
1143}
1144
1145#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1147#[cfg_attr(feature = "schema", derive(JsonSchema))]
1148#[serde(rename_all = "camelCase", deny_unknown_fields)]
1149pub struct FileAssociation {
1150 pub ext: Vec<AssociationExt>,
1152 pub name: Option<String>,
1154 pub description: Option<String>,
1156 #[serde(default)]
1158 pub role: BundleTypeRole,
1159 #[serde(alias = "mime-type")]
1161 pub mime_type: Option<String>,
1162 #[serde(default)]
1164 pub rank: HandlerRank,
1165}
1166
1167#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1169#[cfg_attr(feature = "schema", derive(JsonSchema))]
1170#[serde(rename_all = "camelCase", deny_unknown_fields)]
1171pub struct DeepLinkProtocol {
1172 pub schemes: Vec<String>,
1174 pub name: Option<String>,
1176 #[serde(default)]
1178 pub role: BundleTypeRole,
1179}
1180
1181#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1184#[cfg_attr(feature = "schema", derive(JsonSchema))]
1185#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
1186pub enum BundleResources {
1187 List(Vec<String>),
1189 Map(HashMap<String, String>),
1191}
1192
1193impl BundleResources {
1194 pub fn push(&mut self, path: impl Into<String>) {
1196 match self {
1197 Self::List(l) => l.push(path.into()),
1198 Self::Map(l) => {
1199 let path = path.into();
1200 l.insert(path.clone(), path);
1201 }
1202 }
1203 }
1204}
1205
1206#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1208#[cfg_attr(feature = "schema", derive(JsonSchema))]
1209#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
1210pub enum Updater {
1211 String(V1Compatible),
1213 Bool(bool),
1216}
1217
1218impl Default for Updater {
1219 fn default() -> Self {
1220 Self::Bool(false)
1221 }
1222}
1223
1224#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1226#[cfg_attr(feature = "schema", derive(JsonSchema))]
1227#[serde(rename_all = "camelCase", deny_unknown_fields)]
1228pub enum V1Compatible {
1229 V1Compatible,
1231}
1232
1233#[skip_serializing_none]
1237#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1238#[cfg_attr(feature = "schema", derive(JsonSchema))]
1239#[serde(rename_all = "camelCase", deny_unknown_fields)]
1240pub struct BundleConfig {
1241 #[serde(default)]
1243 pub active: bool,
1244 #[serde(default)]
1246 pub targets: BundleTarget,
1247 #[serde(default)]
1248 pub create_updater_artifacts: Updater,
1250 pub publisher: Option<String>,
1255 pub homepage: Option<String>,
1260 #[serde(default)]
1262 pub icon: Vec<String>,
1263 pub resources: Option<BundleResources>,
1267 pub copyright: Option<String>,
1269 pub license: Option<String>,
1272 #[serde(alias = "license-file")]
1274 pub license_file: Option<PathBuf>,
1275 pub category: Option<String>,
1280 pub file_associations: Option<Vec<FileAssociation>>,
1282 #[serde(alias = "short-description")]
1284 pub short_description: Option<String>,
1285 #[serde(alias = "long-description")]
1287 pub long_description: Option<String>,
1288 #[serde(default, alias = "use-local-tools-dir")]
1296 pub use_local_tools_dir: bool,
1297 #[serde(alias = "external-bin")]
1309 pub external_bin: Option<Vec<String>>,
1310 #[serde(default)]
1312 pub windows: WindowsConfig,
1313 #[serde(default)]
1315 pub linux: LinuxConfig,
1316 #[serde(rename = "macOS", alias = "macos", default)]
1318 pub macos: MacConfig,
1319 #[serde(rename = "iOS", alias = "ios", default)]
1321 pub ios: IosConfig,
1322 #[serde(default)]
1324 pub android: AndroidConfig,
1325}
1326
1327#[derive(Debug, PartialEq, Eq, Serialize, Default, Clone, Copy)]
1329#[serde(rename_all = "camelCase", deny_unknown_fields)]
1330pub struct Color(pub u8, pub u8, pub u8, pub u8);
1331
1332impl From<Color> for (u8, u8, u8, u8) {
1333 fn from(value: Color) -> Self {
1334 (value.0, value.1, value.2, value.3)
1335 }
1336}
1337
1338impl From<Color> for (u8, u8, u8) {
1339 fn from(value: Color) -> Self {
1340 (value.0, value.1, value.2)
1341 }
1342}
1343
1344impl From<(u8, u8, u8, u8)> for Color {
1345 fn from(value: (u8, u8, u8, u8)) -> Self {
1346 Color(value.0, value.1, value.2, value.3)
1347 }
1348}
1349
1350impl From<(u8, u8, u8)> for Color {
1351 fn from(value: (u8, u8, u8)) -> Self {
1352 Color(value.0, value.1, value.2, 255)
1353 }
1354}
1355
1356impl From<Color> for [u8; 4] {
1357 fn from(value: Color) -> Self {
1358 [value.0, value.1, value.2, value.3]
1359 }
1360}
1361
1362impl From<Color> for [u8; 3] {
1363 fn from(value: Color) -> Self {
1364 [value.0, value.1, value.2]
1365 }
1366}
1367
1368impl From<[u8; 4]> for Color {
1369 fn from(value: [u8; 4]) -> Self {
1370 Color(value[0], value[1], value[2], value[3])
1371 }
1372}
1373
1374impl From<[u8; 3]> for Color {
1375 fn from(value: [u8; 3]) -> Self {
1376 Color(value[0], value[1], value[2], 255)
1377 }
1378}
1379
1380impl FromStr for Color {
1381 type Err = String;
1382 fn from_str(mut color: &str) -> Result<Self, Self::Err> {
1383 color = color.trim().strip_prefix('#').unwrap_or(color);
1384 let color = match color.len() {
1385 3 => color.chars()
1387 .flat_map(|c| std::iter::repeat(c).take(2))
1388 .chain(std::iter::repeat('f').take(2))
1389 .collect(),
1390 6 => format!("{color}FF"),
1391 8 => color.to_string(),
1392 _ => return Err("Invalid hex color length, must be either 3, 6 or 8, for example: #fff, #ffffff, or #ffffffff".into()),
1393 };
1394
1395 let r = u8::from_str_radix(&color[0..2], 16).map_err(|e| e.to_string())?;
1396 let g = u8::from_str_radix(&color[2..4], 16).map_err(|e| e.to_string())?;
1397 let b = u8::from_str_radix(&color[4..6], 16).map_err(|e| e.to_string())?;
1398 let a = u8::from_str_radix(&color[6..8], 16).map_err(|e| e.to_string())?;
1399
1400 Ok(Color(r, g, b, a))
1401 }
1402}
1403
1404fn default_alpha() -> u8 {
1405 255
1406}
1407
1408#[derive(Deserialize)]
1409#[cfg_attr(feature = "schema", derive(JsonSchema))]
1410#[serde(untagged)]
1411enum InnerColor {
1412 String(String),
1414 Rgb((u8, u8, u8)),
1416 Rgba((u8, u8, u8, u8)),
1418 RgbaObject {
1420 red: u8,
1421 green: u8,
1422 blue: u8,
1423 #[serde(default = "default_alpha")]
1424 alpha: u8,
1425 },
1426}
1427
1428impl<'de> Deserialize<'de> for Color {
1429 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1430 where
1431 D: Deserializer<'de>,
1432 {
1433 let color = InnerColor::deserialize(deserializer)?;
1434 let color = match color {
1435 InnerColor::String(string) => string.parse().map_err(serde::de::Error::custom)?,
1436 InnerColor::Rgb(rgb) => Color(rgb.0, rgb.1, rgb.2, 255),
1437 InnerColor::Rgba(rgb) => rgb.into(),
1438 InnerColor::RgbaObject {
1439 red,
1440 green,
1441 blue,
1442 alpha,
1443 } => Color(red, green, blue, alpha),
1444 };
1445
1446 Ok(color)
1447 }
1448}
1449
1450#[cfg(feature = "schema")]
1451impl schemars::JsonSchema for Color {
1452 fn schema_name() -> String {
1453 "Color".to_string()
1454 }
1455
1456 fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
1457 let mut schema = schemars::schema_for!(InnerColor).schema;
1458 schema.metadata = None; let any_of = schema.subschemas().any_of.as_mut().unwrap();
1462 let schemars::schema::Schema::Object(str_schema) = any_of.first_mut().unwrap() else {
1463 unreachable!()
1464 };
1465 str_schema.string().pattern = Some("^#?([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$".into());
1466
1467 schema.into()
1468 }
1469}
1470
1471#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1473#[cfg_attr(feature = "schema", derive(JsonSchema))]
1474#[serde(rename_all = "camelCase", deny_unknown_fields)]
1475pub enum BackgroundThrottlingPolicy {
1476 Disabled,
1478 Suspend,
1480 Throttle,
1482}
1483
1484#[skip_serializing_none]
1486#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default)]
1487#[cfg_attr(feature = "schema", derive(JsonSchema))]
1488#[serde(rename_all = "camelCase", deny_unknown_fields)]
1489pub struct WindowEffectsConfig {
1490 pub effects: Vec<WindowEffect>,
1493 pub state: Option<WindowEffectState>,
1495 pub radius: Option<f64>,
1497 pub color: Option<Color>,
1500}
1501
1502#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default)]
1505#[cfg_attr(feature = "schema", derive(JsonSchema))]
1506#[serde(rename_all = "camelCase", deny_unknown_fields)]
1507pub struct PreventOverflowMargin {
1508 pub width: u32,
1510 pub height: u32,
1512}
1513
1514#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1516#[cfg_attr(feature = "schema", derive(JsonSchema))]
1517#[serde(untagged)]
1518pub enum PreventOverflowConfig {
1519 Enable(bool),
1521 Margin(PreventOverflowMargin),
1524}
1525
1526#[skip_serializing_none]
1530#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
1531#[cfg_attr(feature = "schema", derive(JsonSchema))]
1532#[serde(rename_all = "camelCase", deny_unknown_fields)]
1533pub struct WindowConfig {
1534 #[serde(default = "default_window_label")]
1536 pub label: String,
1537 #[serde(default = "default_true")]
1542 pub create: bool,
1543 #[serde(default)]
1545 pub url: WebviewUrl,
1546 #[serde(alias = "user-agent")]
1548 pub user_agent: Option<String>,
1549 #[serde(default = "default_true", alias = "drag-drop-enabled")]
1553 pub drag_drop_enabled: bool,
1554 #[serde(default)]
1556 pub center: bool,
1557 pub x: Option<f64>,
1559 pub y: Option<f64>,
1561 #[serde(default = "default_width")]
1563 pub width: f64,
1564 #[serde(default = "default_height")]
1566 pub height: f64,
1567 #[serde(alias = "min-width")]
1569 pub min_width: Option<f64>,
1570 #[serde(alias = "min-height")]
1572 pub min_height: Option<f64>,
1573 #[serde(alias = "max-width")]
1575 pub max_width: Option<f64>,
1576 #[serde(alias = "max-height")]
1578 pub max_height: Option<f64>,
1579 #[serde(alias = "prevent-overflow")]
1585 pub prevent_overflow: Option<PreventOverflowConfig>,
1586 #[serde(default = "default_true")]
1588 pub resizable: bool,
1589 #[serde(default = "default_true")]
1597 pub maximizable: bool,
1598 #[serde(default = "default_true")]
1604 pub minimizable: bool,
1605 #[serde(default = "default_true")]
1613 pub closable: bool,
1614 #[serde(default = "default_title")]
1616 pub title: String,
1617 #[serde(default)]
1619 pub fullscreen: bool,
1620 #[serde(default = "default_true")]
1622 pub focus: bool,
1623 #[serde(default)]
1628 pub transparent: bool,
1629 #[serde(default)]
1631 pub maximized: bool,
1632 #[serde(default = "default_true")]
1634 pub visible: bool,
1635 #[serde(default = "default_true")]
1637 pub decorations: bool,
1638 #[serde(default, alias = "always-on-bottom")]
1640 pub always_on_bottom: bool,
1641 #[serde(default, alias = "always-on-top")]
1643 pub always_on_top: bool,
1644 #[serde(default, alias = "visible-on-all-workspaces")]
1650 pub visible_on_all_workspaces: bool,
1651 #[serde(default, alias = "content-protected")]
1653 pub content_protected: bool,
1654 #[serde(default, alias = "skip-taskbar")]
1656 pub skip_taskbar: bool,
1657 pub window_classname: Option<String>,
1659 pub theme: Option<crate::Theme>,
1661 #[serde(default, alias = "title-bar-style")]
1663 pub title_bar_style: TitleBarStyle,
1664 #[serde(default, alias = "traffic-light-position")]
1668 pub traffic_light_position: Option<LogicalPosition>,
1669 #[serde(default, alias = "hidden-title")]
1671 pub hidden_title: bool,
1672 #[serde(default, alias = "accept-first-mouse")]
1674 pub accept_first_mouse: bool,
1675 #[serde(default, alias = "tabbing-identifier")]
1682 pub tabbing_identifier: Option<String>,
1683 #[serde(default, alias = "additional-browser-args")]
1686 pub additional_browser_args: Option<String>,
1687 #[serde(default = "default_true")]
1697 pub shadow: bool,
1698 #[serde(default, alias = "window-effects")]
1707 pub window_effects: Option<WindowEffectsConfig>,
1708 #[serde(default)]
1714 pub incognito: bool,
1715 pub parent: Option<String>,
1727 #[serde(alias = "proxy-url")]
1735 pub proxy_url: Option<Url>,
1736 #[serde(default, alias = "zoom-hotkeys-enabled")]
1746 pub zoom_hotkeys_enabled: bool,
1747 #[serde(default, alias = "browser-extensions-enabled")]
1754 pub browser_extensions_enabled: bool,
1755
1756 #[serde(default, alias = "use-https-scheme")]
1766 pub use_https_scheme: bool,
1767 pub devtools: Option<bool>,
1777
1778 #[serde(alias = "background-color")]
1786 pub background_color: Option<Color>,
1787
1788 #[serde(default, alias = "background-throttling")]
1803 pub background_throttling: Option<BackgroundThrottlingPolicy>,
1804 #[serde(default, alias = "javascript-disabled")]
1806 pub javascript_disabled: bool,
1807 #[serde(default = "default_true", alias = "allow-link-preview")]
1810 pub allow_link_preview: bool,
1811 #[serde(
1816 default,
1817 alias = "disable-input-accessory-view",
1818 alias = "disable_input_accessory_view"
1819 )]
1820 pub disable_input_accessory_view: bool,
1821}
1822
1823impl Default for WindowConfig {
1824 fn default() -> Self {
1825 Self {
1826 label: default_window_label(),
1827 url: WebviewUrl::default(),
1828 create: true,
1829 user_agent: None,
1830 drag_drop_enabled: true,
1831 center: false,
1832 x: None,
1833 y: None,
1834 width: default_width(),
1835 height: default_height(),
1836 min_width: None,
1837 min_height: None,
1838 max_width: None,
1839 max_height: None,
1840 prevent_overflow: None,
1841 resizable: true,
1842 maximizable: true,
1843 minimizable: true,
1844 closable: true,
1845 title: default_title(),
1846 fullscreen: false,
1847 focus: false,
1848 transparent: false,
1849 maximized: false,
1850 visible: true,
1851 decorations: true,
1852 always_on_bottom: false,
1853 always_on_top: false,
1854 visible_on_all_workspaces: false,
1855 content_protected: false,
1856 skip_taskbar: false,
1857 window_classname: None,
1858 theme: None,
1859 title_bar_style: Default::default(),
1860 traffic_light_position: None,
1861 hidden_title: false,
1862 accept_first_mouse: false,
1863 tabbing_identifier: None,
1864 additional_browser_args: None,
1865 shadow: true,
1866 window_effects: None,
1867 incognito: false,
1868 parent: None,
1869 proxy_url: None,
1870 zoom_hotkeys_enabled: false,
1871 browser_extensions_enabled: false,
1872 use_https_scheme: false,
1873 devtools: None,
1874 background_color: None,
1875 background_throttling: None,
1876 javascript_disabled: false,
1877 allow_link_preview: true,
1878 disable_input_accessory_view: false,
1879 }
1880 }
1881}
1882
1883fn default_window_label() -> String {
1884 "main".to_string()
1885}
1886
1887fn default_width() -> f64 {
1888 800f64
1889}
1890
1891fn default_height() -> f64 {
1892 600f64
1893}
1894
1895fn default_title() -> String {
1896 "Tauri App".to_string()
1897}
1898
1899#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1902#[cfg_attr(feature = "schema", derive(JsonSchema))]
1903#[serde(rename_all = "camelCase", untagged)]
1904pub enum CspDirectiveSources {
1905 Inline(String),
1907 List(Vec<String>),
1909}
1910
1911impl Default for CspDirectiveSources {
1912 fn default() -> Self {
1913 Self::List(Vec::new())
1914 }
1915}
1916
1917impl From<CspDirectiveSources> for Vec<String> {
1918 fn from(sources: CspDirectiveSources) -> Self {
1919 match sources {
1920 CspDirectiveSources::Inline(source) => source.split(' ').map(|s| s.to_string()).collect(),
1921 CspDirectiveSources::List(l) => l,
1922 }
1923 }
1924}
1925
1926impl CspDirectiveSources {
1927 pub fn contains(&self, source: &str) -> bool {
1929 match self {
1930 Self::Inline(s) => s.contains(&format!("{source} ")) || s.contains(&format!(" {source}")),
1931 Self::List(l) => l.contains(&source.into()),
1932 }
1933 }
1934
1935 pub fn push<S: AsRef<str>>(&mut self, source: S) {
1937 match self {
1938 Self::Inline(s) => {
1939 s.push(' ');
1940 s.push_str(source.as_ref());
1941 }
1942 Self::List(l) => {
1943 l.push(source.as_ref().to_string());
1944 }
1945 }
1946 }
1947
1948 pub fn extend(&mut self, sources: Vec<String>) {
1950 for s in sources {
1951 self.push(s);
1952 }
1953 }
1954}
1955
1956#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1959#[cfg_attr(feature = "schema", derive(JsonSchema))]
1960#[serde(rename_all = "camelCase", untagged)]
1961pub enum Csp {
1962 Policy(String),
1964 DirectiveMap(HashMap<String, CspDirectiveSources>),
1966}
1967
1968impl From<HashMap<String, CspDirectiveSources>> for Csp {
1969 fn from(map: HashMap<String, CspDirectiveSources>) -> Self {
1970 Self::DirectiveMap(map)
1971 }
1972}
1973
1974impl From<Csp> for HashMap<String, CspDirectiveSources> {
1975 fn from(csp: Csp) -> Self {
1976 match csp {
1977 Csp::Policy(policy) => {
1978 let mut map = HashMap::new();
1979 for directive in policy.split(';') {
1980 let mut tokens = directive.trim().split(' ');
1981 if let Some(directive) = tokens.next() {
1982 let sources = tokens.map(|s| s.to_string()).collect::<Vec<String>>();
1983 map.insert(directive.to_string(), CspDirectiveSources::List(sources));
1984 }
1985 }
1986 map
1987 }
1988 Csp::DirectiveMap(m) => m,
1989 }
1990 }
1991}
1992
1993impl Display for Csp {
1994 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1995 match self {
1996 Self::Policy(s) => write!(f, "{s}"),
1997 Self::DirectiveMap(m) => {
1998 let len = m.len();
1999 let mut i = 0;
2000 for (directive, sources) in m {
2001 let sources: Vec<String> = sources.clone().into();
2002 write!(f, "{} {}", directive, sources.join(" "))?;
2003 i += 1;
2004 if i != len {
2005 write!(f, "; ")?;
2006 }
2007 }
2008 Ok(())
2009 }
2010 }
2011 }
2012}
2013
2014#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2016#[serde(untagged)]
2017#[cfg_attr(feature = "schema", derive(JsonSchema))]
2018pub enum DisabledCspModificationKind {
2019 Flag(bool),
2022 List(Vec<String>),
2024}
2025
2026impl DisabledCspModificationKind {
2027 pub fn can_modify(&self, directive: &str) -> bool {
2029 match self {
2030 Self::Flag(f) => !f,
2031 Self::List(l) => !l.contains(&directive.into()),
2032 }
2033 }
2034}
2035
2036impl Default for DisabledCspModificationKind {
2037 fn default() -> Self {
2038 Self::Flag(false)
2039 }
2040}
2041
2042#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2051#[serde(untagged)]
2052#[cfg_attr(feature = "schema", derive(JsonSchema))]
2053pub enum FsScope {
2054 AllowedPaths(Vec<PathBuf>),
2056 #[serde(rename_all = "camelCase")]
2058 Scope {
2059 #[serde(default)]
2061 allow: Vec<PathBuf>,
2062 #[serde(default)]
2065 deny: Vec<PathBuf>,
2066 #[serde(alias = "require-literal-leading-dot")]
2075 require_literal_leading_dot: Option<bool>,
2076 },
2077}
2078
2079impl Default for FsScope {
2080 fn default() -> Self {
2081 Self::AllowedPaths(Vec::new())
2082 }
2083}
2084
2085impl FsScope {
2086 pub fn allowed_paths(&self) -> &Vec<PathBuf> {
2088 match self {
2089 Self::AllowedPaths(p) => p,
2090 Self::Scope { allow, .. } => allow,
2091 }
2092 }
2093
2094 pub fn forbidden_paths(&self) -> Option<&Vec<PathBuf>> {
2096 match self {
2097 Self::AllowedPaths(_) => None,
2098 Self::Scope { deny, .. } => Some(deny),
2099 }
2100 }
2101}
2102
2103#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2107#[cfg_attr(feature = "schema", derive(JsonSchema))]
2108#[serde(rename_all = "camelCase", deny_unknown_fields)]
2109pub struct AssetProtocolConfig {
2110 #[serde(default)]
2112 pub scope: FsScope,
2113 #[serde(default)]
2115 pub enable: bool,
2116}
2117
2118#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2122#[cfg_attr(feature = "schema", derive(JsonSchema))]
2123#[serde(rename_all = "camelCase", untagged)]
2124pub enum HeaderSource {
2125 Inline(String),
2127 List(Vec<String>),
2129 Map(HashMap<String, String>),
2131}
2132
2133impl Display for HeaderSource {
2134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2135 match self {
2136 Self::Inline(s) => write!(f, "{s}"),
2137 Self::List(l) => write!(f, "{}", l.join(", ")),
2138 Self::Map(m) => {
2139 let len = m.len();
2140 let mut i = 0;
2141 for (key, value) in m {
2142 write!(f, "{} {}", key, value)?;
2143 i += 1;
2144 if i != len {
2145 write!(f, "; ")?;
2146 }
2147 }
2148 Ok(())
2149 }
2150 }
2151 }
2152}
2153
2154pub trait HeaderAddition {
2158 fn add_configured_headers(self, headers: Option<&HeaderConfig>) -> http::response::Builder;
2160}
2161
2162impl HeaderAddition for Builder {
2163 fn add_configured_headers(mut self, headers: Option<&HeaderConfig>) -> http::response::Builder {
2167 if let Some(headers) = headers {
2168 if let Some(value) = &headers.access_control_allow_credentials {
2170 self = self.header("Access-Control-Allow-Credentials", value.to_string());
2171 };
2172
2173 if let Some(value) = &headers.access_control_allow_headers {
2175 self = self.header("Access-Control-Allow-Headers", value.to_string());
2176 };
2177
2178 if let Some(value) = &headers.access_control_allow_methods {
2180 self = self.header("Access-Control-Allow-Methods", value.to_string());
2181 };
2182
2183 if let Some(value) = &headers.access_control_expose_headers {
2185 self = self.header("Access-Control-Expose-Headers", value.to_string());
2186 };
2187
2188 if let Some(value) = &headers.access_control_max_age {
2190 self = self.header("Access-Control-Max-Age", value.to_string());
2191 };
2192
2193 if let Some(value) = &headers.cross_origin_embedder_policy {
2195 self = self.header("Cross-Origin-Embedder-Policy", value.to_string());
2196 };
2197
2198 if let Some(value) = &headers.cross_origin_opener_policy {
2200 self = self.header("Cross-Origin-Opener-Policy", value.to_string());
2201 };
2202
2203 if let Some(value) = &headers.cross_origin_resource_policy {
2205 self = self.header("Cross-Origin-Resource-Policy", value.to_string());
2206 };
2207
2208 if let Some(value) = &headers.permissions_policy {
2210 self = self.header("Permission-Policy", value.to_string());
2211 };
2212
2213 if let Some(value) = &headers.timing_allow_origin {
2215 self = self.header("Timing-Allow-Origin", value.to_string());
2216 };
2217
2218 if let Some(value) = &headers.x_content_type_options {
2220 self = self.header("X-Content-Type-Options", value.to_string());
2221 };
2222
2223 if let Some(value) = &headers.tauri_custom_header {
2225 self = self.header("Tauri-Custom-Header", value.to_string());
2227 };
2228 }
2229 self
2230 }
2231}
2232
2233#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2285#[cfg_attr(feature = "schema", derive(JsonSchema))]
2286#[serde(deny_unknown_fields)]
2287pub struct HeaderConfig {
2288 #[serde(rename = "Access-Control-Allow-Credentials")]
2293 pub access_control_allow_credentials: Option<HeaderSource>,
2294 #[serde(rename = "Access-Control-Allow-Headers")]
2302 pub access_control_allow_headers: Option<HeaderSource>,
2303 #[serde(rename = "Access-Control-Allow-Methods")]
2308 pub access_control_allow_methods: Option<HeaderSource>,
2309 #[serde(rename = "Access-Control-Expose-Headers")]
2315 pub access_control_expose_headers: Option<HeaderSource>,
2316 #[serde(rename = "Access-Control-Max-Age")]
2323 pub access_control_max_age: Option<HeaderSource>,
2324 #[serde(rename = "Cross-Origin-Embedder-Policy")]
2329 pub cross_origin_embedder_policy: Option<HeaderSource>,
2330 #[serde(rename = "Cross-Origin-Opener-Policy")]
2337 pub cross_origin_opener_policy: Option<HeaderSource>,
2338 #[serde(rename = "Cross-Origin-Resource-Policy")]
2343 pub cross_origin_resource_policy: Option<HeaderSource>,
2344 #[serde(rename = "Permissions-Policy")]
2349 pub permissions_policy: Option<HeaderSource>,
2350 #[serde(rename = "Timing-Allow-Origin")]
2356 pub timing_allow_origin: Option<HeaderSource>,
2357 #[serde(rename = "X-Content-Type-Options")]
2364 pub x_content_type_options: Option<HeaderSource>,
2365 #[serde(rename = "Tauri-Custom-Header")]
2370 pub tauri_custom_header: Option<HeaderSource>,
2371}
2372
2373impl HeaderConfig {
2374 pub fn new() -> Self {
2376 HeaderConfig {
2377 access_control_allow_credentials: None,
2378 access_control_allow_methods: None,
2379 access_control_allow_headers: None,
2380 access_control_expose_headers: None,
2381 access_control_max_age: None,
2382 cross_origin_embedder_policy: None,
2383 cross_origin_opener_policy: None,
2384 cross_origin_resource_policy: None,
2385 permissions_policy: None,
2386 timing_allow_origin: None,
2387 x_content_type_options: None,
2388 tauri_custom_header: None,
2389 }
2390 }
2391}
2392
2393#[skip_serializing_none]
2397#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
2398#[cfg_attr(feature = "schema", derive(JsonSchema))]
2399#[serde(rename_all = "camelCase", deny_unknown_fields)]
2400pub struct SecurityConfig {
2401 pub csp: Option<Csp>,
2407 #[serde(alias = "dev-csp")]
2412 pub dev_csp: Option<Csp>,
2413 #[serde(default, alias = "freeze-prototype")]
2415 pub freeze_prototype: bool,
2416 #[serde(default, alias = "dangerous-disable-asset-csp-modification")]
2429 pub dangerous_disable_asset_csp_modification: DisabledCspModificationKind,
2430 #[serde(default, alias = "asset-protocol")]
2432 pub asset_protocol: AssetProtocolConfig,
2433 #[serde(default)]
2435 pub pattern: PatternKind,
2436 #[serde(default)]
2440 pub capabilities: Vec<CapabilityEntry>,
2441 #[serde(default)]
2444 pub headers: Option<HeaderConfig>,
2445}
2446
2447#[derive(Debug, Clone, PartialEq, Serialize)]
2449#[cfg_attr(feature = "schema", derive(JsonSchema))]
2450#[serde(untagged)]
2451pub enum CapabilityEntry {
2452 Inlined(Capability),
2454 Reference(String),
2456}
2457
2458impl<'de> Deserialize<'de> for CapabilityEntry {
2459 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2460 where
2461 D: Deserializer<'de>,
2462 {
2463 UntaggedEnumVisitor::new()
2464 .string(|string| Ok(Self::Reference(string.to_owned())))
2465 .map(|map| map.deserialize::<Capability>().map(Self::Inlined))
2466 .deserialize(deserializer)
2467 }
2468}
2469
2470#[skip_serializing_none]
2472#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
2473#[serde(rename_all = "lowercase", tag = "use", content = "options")]
2474#[cfg_attr(feature = "schema", derive(JsonSchema))]
2475pub enum PatternKind {
2476 Brownfield,
2478 Isolation {
2480 dir: PathBuf,
2482 },
2483}
2484
2485impl Default for PatternKind {
2486 fn default() -> Self {
2487 Self::Brownfield
2488 }
2489}
2490
2491#[skip_serializing_none]
2495#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
2496#[cfg_attr(feature = "schema", derive(JsonSchema))]
2497#[serde(rename_all = "camelCase", deny_unknown_fields)]
2498pub struct AppConfig {
2499 #[serde(default)]
2501 pub windows: Vec<WindowConfig>,
2502 #[serde(default)]
2504 pub security: SecurityConfig,
2505 #[serde(alias = "tray-icon")]
2507 pub tray_icon: Option<TrayIconConfig>,
2508 #[serde(rename = "macOSPrivateApi", alias = "macos-private-api", default)]
2510 pub macos_private_api: bool,
2511 #[serde(default, alias = "with-global-tauri")]
2513 pub with_global_tauri: bool,
2514 #[serde(rename = "enableGTKAppId", alias = "enable-gtk-app-id", default)]
2516 pub enable_gtk_app_id: bool,
2517}
2518
2519impl AppConfig {
2520 pub fn all_features() -> Vec<&'static str> {
2522 vec![
2523 "tray-icon",
2524 "macos-private-api",
2525 "protocol-asset",
2526 "isolation",
2527 ]
2528 }
2529
2530 pub fn features(&self) -> Vec<&str> {
2532 let mut features = Vec::new();
2533 if self.tray_icon.is_some() {
2534 features.push("tray-icon");
2535 }
2536 if self.macos_private_api {
2537 features.push("macos-private-api");
2538 }
2539 if self.security.asset_protocol.enable {
2540 features.push("protocol-asset");
2541 }
2542
2543 if let PatternKind::Isolation { .. } = self.security.pattern {
2544 features.push("isolation");
2545 }
2546
2547 features.sort_unstable();
2548 features
2549 }
2550}
2551
2552#[skip_serializing_none]
2556#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2557#[cfg_attr(feature = "schema", derive(JsonSchema))]
2558#[serde(rename_all = "camelCase", deny_unknown_fields)]
2559pub struct TrayIconConfig {
2560 pub id: Option<String>,
2562 #[serde(alias = "icon-path")]
2568 pub icon_path: PathBuf,
2569 #[serde(default, alias = "icon-as-template")]
2571 pub icon_as_template: bool,
2572 #[serde(default = "default_true", alias = "menu-on-left-click")]
2578 #[deprecated(since = "2.2.0", note = "Use `show_menu_on_left_click` instead.")]
2579 pub menu_on_left_click: bool,
2580 #[serde(default = "default_true", alias = "show-menu-on-left-click")]
2586 pub show_menu_on_left_click: bool,
2587 pub title: Option<String>,
2589 pub tooltip: Option<String>,
2591}
2592
2593#[skip_serializing_none]
2595#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2596#[cfg_attr(feature = "schema", derive(JsonSchema))]
2597#[serde(rename_all = "camelCase", deny_unknown_fields)]
2598pub struct IosConfig {
2599 pub template: Option<PathBuf>,
2603 pub frameworks: Option<Vec<String>>,
2607 #[serde(alias = "development-team")]
2610 pub development_team: Option<String>,
2611 #[serde(alias = "bundle-version")]
2615 pub bundle_version: Option<String>,
2616 #[serde(
2620 alias = "minimum-system-version",
2621 default = "ios_minimum_system_version"
2622 )]
2623 pub minimum_system_version: String,
2624}
2625
2626impl Default for IosConfig {
2627 fn default() -> Self {
2628 Self {
2629 template: None,
2630 frameworks: None,
2631 development_team: None,
2632 bundle_version: None,
2633 minimum_system_version: ios_minimum_system_version(),
2634 }
2635 }
2636}
2637
2638#[skip_serializing_none]
2640#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2641#[cfg_attr(feature = "schema", derive(JsonSchema))]
2642#[serde(rename_all = "camelCase", deny_unknown_fields)]
2643pub struct AndroidConfig {
2644 #[serde(alias = "min-sdk-version", default = "default_min_sdk_version")]
2647 pub min_sdk_version: u32,
2648
2649 #[serde(alias = "version-code")]
2655 #[cfg_attr(feature = "schema", validate(range(min = 1, max = 2_100_000_000)))]
2656 pub version_code: Option<u32>,
2657}
2658
2659impl Default for AndroidConfig {
2660 fn default() -> Self {
2661 Self {
2662 min_sdk_version: default_min_sdk_version(),
2663 version_code: None,
2664 }
2665 }
2666}
2667
2668fn default_min_sdk_version() -> u32 {
2669 24
2670}
2671
2672#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2674#[cfg_attr(feature = "schema", derive(JsonSchema))]
2675#[serde(untagged, deny_unknown_fields)]
2676#[non_exhaustive]
2677pub enum FrontendDist {
2678 Url(Url),
2680 Directory(PathBuf),
2682 Files(Vec<PathBuf>),
2684}
2685
2686impl std::fmt::Display for FrontendDist {
2687 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2688 match self {
2689 Self::Url(url) => write!(f, "{url}"),
2690 Self::Directory(p) => write!(f, "{}", p.display()),
2691 Self::Files(files) => write!(f, "{}", serde_json::to_string(files).unwrap()),
2692 }
2693 }
2694}
2695
2696#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2698#[cfg_attr(feature = "schema", derive(JsonSchema))]
2699#[serde(rename_all = "camelCase", untagged)]
2700pub enum BeforeDevCommand {
2701 Script(String),
2703 ScriptWithOptions {
2705 script: String,
2707 cwd: Option<String>,
2709 #[serde(default)]
2711 wait: bool,
2712 },
2713}
2714
2715#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2717#[cfg_attr(feature = "schema", derive(JsonSchema))]
2718#[serde(rename_all = "camelCase", untagged)]
2719pub enum HookCommand {
2720 Script(String),
2722 ScriptWithOptions {
2724 script: String,
2726 cwd: Option<String>,
2728 },
2729}
2730
2731#[skip_serializing_none]
2735#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Default)]
2736#[cfg_attr(feature = "schema", derive(JsonSchema))]
2737#[serde(rename_all = "camelCase", deny_unknown_fields)]
2738pub struct BuildConfig {
2739 pub runner: Option<String>,
2741 #[serde(alias = "dev-url")]
2749 pub dev_url: Option<Url>,
2750 #[serde(alias = "frontend-dist")]
2764 pub frontend_dist: Option<FrontendDist>,
2765 #[serde(alias = "before-dev-command")]
2769 pub before_dev_command: Option<BeforeDevCommand>,
2770 #[serde(alias = "before-build-command")]
2774 pub before_build_command: Option<HookCommand>,
2775 #[serde(alias = "before-bundle-command")]
2779 pub before_bundle_command: Option<HookCommand>,
2780 pub features: Option<Vec<String>>,
2782 #[serde(alias = "remove-unused-commands", default)]
2790 pub remove_unused_commands: bool,
2791}
2792
2793#[derive(Debug, PartialEq, Eq)]
2794struct PackageVersion(String);
2795
2796impl<'d> serde::Deserialize<'d> for PackageVersion {
2797 fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {
2798 struct PackageVersionVisitor;
2799
2800 impl Visitor<'_> for PackageVersionVisitor {
2801 type Value = PackageVersion;
2802
2803 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2804 write!(
2805 formatter,
2806 "a semver string or a path to a package.json file"
2807 )
2808 }
2809
2810 fn visit_str<E: DeError>(self, value: &str) -> Result<PackageVersion, E> {
2811 let path = PathBuf::from(value);
2812 if path.exists() {
2813 let json_str = read_to_string(&path)
2814 .map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?;
2815 let package_json: serde_json::Value = serde_json::from_str(&json_str)
2816 .map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?;
2817 if let Some(obj) = package_json.as_object() {
2818 let version = obj
2819 .get("version")
2820 .ok_or_else(|| DeError::custom("JSON must contain a `version` field"))?
2821 .as_str()
2822 .ok_or_else(|| {
2823 DeError::custom(format!("`{} > version` must be a string", path.display()))
2824 })?;
2825 Ok(PackageVersion(
2826 Version::from_str(version)
2827 .map_err(|_| DeError::custom("`package > version` must be a semver string"))?
2828 .to_string(),
2829 ))
2830 } else {
2831 Err(DeError::custom(
2832 "`package > version` value is not a path to a JSON object",
2833 ))
2834 }
2835 } else {
2836 Ok(PackageVersion(
2837 Version::from_str(value)
2838 .map_err(|_| DeError::custom("`package > version` must be a semver string"))?
2839 .to_string(),
2840 ))
2841 }
2842 }
2843 }
2844
2845 deserializer.deserialize_string(PackageVersionVisitor {})
2846 }
2847}
2848
2849fn version_deserializer<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
2850where
2851 D: Deserializer<'de>,
2852{
2853 Option::<PackageVersion>::deserialize(deserializer).map(|v| v.map(|v| v.0))
2854}
2855
2856#[skip_serializing_none]
2922#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
2923#[cfg_attr(feature = "schema", derive(JsonSchema))]
2924#[serde(rename_all = "camelCase", deny_unknown_fields)]
2925pub struct Config {
2926 #[serde(rename = "$schema")]
2928 pub schema: Option<String>,
2929 #[serde(alias = "product-name")]
2931 #[cfg_attr(feature = "schema", validate(regex(pattern = "^[^/\\:*?\"<>|]+$")))]
2932 pub product_name: Option<String>,
2933 #[serde(alias = "main-binary-name")]
2935 pub main_binary_name: Option<String>,
2936 #[serde(deserialize_with = "version_deserializer", default)]
2952 pub version: Option<String>,
2953 pub identifier: String,
2959 #[serde(default)]
2961 pub app: AppConfig,
2962 #[serde(default)]
2964 pub build: BuildConfig,
2965 #[serde(default)]
2967 pub bundle: BundleConfig,
2968 #[serde(default)]
2970 pub plugins: PluginConfig,
2971}
2972
2973#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
2977#[cfg_attr(feature = "schema", derive(JsonSchema))]
2978pub struct PluginConfig(pub HashMap<String, JsonValue>);
2979
2980#[cfg(feature = "build")]
2986mod build {
2987 use super::*;
2988 use crate::{literal_struct, tokens::*};
2989 use proc_macro2::TokenStream;
2990 use quote::{quote, ToTokens, TokenStreamExt};
2991 use std::convert::identity;
2992
2993 impl ToTokens for WebviewUrl {
2994 fn to_tokens(&self, tokens: &mut TokenStream) {
2995 let prefix = quote! { ::tauri::utils::config::WebviewUrl };
2996
2997 tokens.append_all(match self {
2998 Self::App(path) => {
2999 let path = path_buf_lit(path);
3000 quote! { #prefix::App(#path) }
3001 }
3002 Self::External(url) => {
3003 let url = url_lit(url);
3004 quote! { #prefix::External(#url) }
3005 }
3006 Self::CustomProtocol(url) => {
3007 let url = url_lit(url);
3008 quote! { #prefix::CustomProtocol(#url) }
3009 }
3010 })
3011 }
3012 }
3013
3014 impl ToTokens for BackgroundThrottlingPolicy {
3015 fn to_tokens(&self, tokens: &mut TokenStream) {
3016 let prefix = quote! { ::tauri::utils::config::BackgroundThrottlingPolicy };
3017 tokens.append_all(match self {
3018 Self::Disabled => quote! { #prefix::Disabled },
3019 Self::Throttle => quote! { #prefix::Throttle },
3020 Self::Suspend => quote! { #prefix::Suspend },
3021 })
3022 }
3023 }
3024
3025 impl ToTokens for crate::Theme {
3026 fn to_tokens(&self, tokens: &mut TokenStream) {
3027 let prefix = quote! { ::tauri::utils::Theme };
3028
3029 tokens.append_all(match self {
3030 Self::Light => quote! { #prefix::Light },
3031 Self::Dark => quote! { #prefix::Dark },
3032 })
3033 }
3034 }
3035
3036 impl ToTokens for Color {
3037 fn to_tokens(&self, tokens: &mut TokenStream) {
3038 let Color(r, g, b, a) = self;
3039 tokens.append_all(quote! {::tauri::utils::config::Color(#r,#g,#b,#a)});
3040 }
3041 }
3042 impl ToTokens for WindowEffectsConfig {
3043 fn to_tokens(&self, tokens: &mut TokenStream) {
3044 let effects = vec_lit(self.effects.clone(), |d| d);
3045 let state = opt_lit(self.state.as_ref());
3046 let radius = opt_lit(self.radius.as_ref());
3047 let color = opt_lit(self.color.as_ref());
3048
3049 literal_struct!(
3050 tokens,
3051 ::tauri::utils::config::WindowEffectsConfig,
3052 effects,
3053 state,
3054 radius,
3055 color
3056 )
3057 }
3058 }
3059
3060 impl ToTokens for crate::TitleBarStyle {
3061 fn to_tokens(&self, tokens: &mut TokenStream) {
3062 let prefix = quote! { ::tauri::utils::TitleBarStyle };
3063
3064 tokens.append_all(match self {
3065 Self::Visible => quote! { #prefix::Visible },
3066 Self::Transparent => quote! { #prefix::Transparent },
3067 Self::Overlay => quote! { #prefix::Overlay },
3068 })
3069 }
3070 }
3071
3072 impl ToTokens for LogicalPosition {
3073 fn to_tokens(&self, tokens: &mut TokenStream) {
3074 let LogicalPosition { x, y } = self;
3075 literal_struct!(tokens, ::tauri::utils::config::LogicalPosition, x, y)
3076 }
3077 }
3078
3079 impl ToTokens for crate::WindowEffect {
3080 fn to_tokens(&self, tokens: &mut TokenStream) {
3081 let prefix = quote! { ::tauri::utils::WindowEffect };
3082
3083 #[allow(deprecated)]
3084 tokens.append_all(match self {
3085 WindowEffect::AppearanceBased => quote! { #prefix::AppearanceBased},
3086 WindowEffect::Light => quote! { #prefix::Light},
3087 WindowEffect::Dark => quote! { #prefix::Dark},
3088 WindowEffect::MediumLight => quote! { #prefix::MediumLight},
3089 WindowEffect::UltraDark => quote! { #prefix::UltraDark},
3090 WindowEffect::Titlebar => quote! { #prefix::Titlebar},
3091 WindowEffect::Selection => quote! { #prefix::Selection},
3092 WindowEffect::Menu => quote! { #prefix::Menu},
3093 WindowEffect::Popover => quote! { #prefix::Popover},
3094 WindowEffect::Sidebar => quote! { #prefix::Sidebar},
3095 WindowEffect::HeaderView => quote! { #prefix::HeaderView},
3096 WindowEffect::Sheet => quote! { #prefix::Sheet},
3097 WindowEffect::WindowBackground => quote! { #prefix::WindowBackground},
3098 WindowEffect::HudWindow => quote! { #prefix::HudWindow},
3099 WindowEffect::FullScreenUI => quote! { #prefix::FullScreenUI},
3100 WindowEffect::Tooltip => quote! { #prefix::Tooltip},
3101 WindowEffect::ContentBackground => quote! { #prefix::ContentBackground},
3102 WindowEffect::UnderWindowBackground => quote! { #prefix::UnderWindowBackground},
3103 WindowEffect::UnderPageBackground => quote! { #prefix::UnderPageBackground},
3104 WindowEffect::Mica => quote! { #prefix::Mica},
3105 WindowEffect::MicaDark => quote! { #prefix::MicaDark},
3106 WindowEffect::MicaLight => quote! { #prefix::MicaLight},
3107 WindowEffect::Blur => quote! { #prefix::Blur},
3108 WindowEffect::Acrylic => quote! { #prefix::Acrylic},
3109 WindowEffect::Tabbed => quote! { #prefix::Tabbed },
3110 WindowEffect::TabbedDark => quote! { #prefix::TabbedDark },
3111 WindowEffect::TabbedLight => quote! { #prefix::TabbedLight },
3112 })
3113 }
3114 }
3115
3116 impl ToTokens for crate::WindowEffectState {
3117 fn to_tokens(&self, tokens: &mut TokenStream) {
3118 let prefix = quote! { ::tauri::utils::WindowEffectState };
3119
3120 #[allow(deprecated)]
3121 tokens.append_all(match self {
3122 WindowEffectState::Active => quote! { #prefix::Active},
3123 WindowEffectState::FollowsWindowActiveState => quote! { #prefix::FollowsWindowActiveState},
3124 WindowEffectState::Inactive => quote! { #prefix::Inactive},
3125 })
3126 }
3127 }
3128
3129 impl ToTokens for PreventOverflowMargin {
3130 fn to_tokens(&self, tokens: &mut TokenStream) {
3131 let width = self.width;
3132 let height = self.height;
3133
3134 literal_struct!(
3135 tokens,
3136 ::tauri::utils::config::PreventOverflowMargin,
3137 width,
3138 height
3139 )
3140 }
3141 }
3142
3143 impl ToTokens for PreventOverflowConfig {
3144 fn to_tokens(&self, tokens: &mut TokenStream) {
3145 let prefix = quote! { ::tauri::utils::config::PreventOverflowConfig };
3146
3147 #[allow(deprecated)]
3148 tokens.append_all(match self {
3149 Self::Enable(enable) => quote! { #prefix::Enable(#enable) },
3150 Self::Margin(margin) => quote! { #prefix::Margin(#margin) },
3151 })
3152 }
3153 }
3154
3155 impl ToTokens for WindowConfig {
3156 fn to_tokens(&self, tokens: &mut TokenStream) {
3157 let label = str_lit(&self.label);
3158 let create = &self.create;
3159 let url = &self.url;
3160 let user_agent = opt_str_lit(self.user_agent.as_ref());
3161 let drag_drop_enabled = self.drag_drop_enabled;
3162 let center = self.center;
3163 let x = opt_lit(self.x.as_ref());
3164 let y = opt_lit(self.y.as_ref());
3165 let width = self.width;
3166 let height = self.height;
3167 let min_width = opt_lit(self.min_width.as_ref());
3168 let min_height = opt_lit(self.min_height.as_ref());
3169 let max_width = opt_lit(self.max_width.as_ref());
3170 let max_height = opt_lit(self.max_height.as_ref());
3171 let prevent_overflow = opt_lit(self.prevent_overflow.as_ref());
3172 let resizable = self.resizable;
3173 let maximizable = self.maximizable;
3174 let minimizable = self.minimizable;
3175 let closable = self.closable;
3176 let title = str_lit(&self.title);
3177 let proxy_url = opt_lit(self.proxy_url.as_ref().map(url_lit).as_ref());
3178 let fullscreen = self.fullscreen;
3179 let focus = self.focus;
3180 let transparent = self.transparent;
3181 let maximized = self.maximized;
3182 let visible = self.visible;
3183 let decorations = self.decorations;
3184 let always_on_bottom = self.always_on_bottom;
3185 let always_on_top = self.always_on_top;
3186 let visible_on_all_workspaces = self.visible_on_all_workspaces;
3187 let content_protected = self.content_protected;
3188 let skip_taskbar = self.skip_taskbar;
3189 let window_classname = opt_str_lit(self.window_classname.as_ref());
3190 let theme = opt_lit(self.theme.as_ref());
3191 let title_bar_style = &self.title_bar_style;
3192 let traffic_light_position = opt_lit(self.traffic_light_position.as_ref());
3193 let hidden_title = self.hidden_title;
3194 let accept_first_mouse = self.accept_first_mouse;
3195 let tabbing_identifier = opt_str_lit(self.tabbing_identifier.as_ref());
3196 let additional_browser_args = opt_str_lit(self.additional_browser_args.as_ref());
3197 let shadow = self.shadow;
3198 let window_effects = opt_lit(self.window_effects.as_ref());
3199 let incognito = self.incognito;
3200 let parent = opt_str_lit(self.parent.as_ref());
3201 let zoom_hotkeys_enabled = self.zoom_hotkeys_enabled;
3202 let browser_extensions_enabled = self.browser_extensions_enabled;
3203 let use_https_scheme = self.use_https_scheme;
3204 let devtools = opt_lit(self.devtools.as_ref());
3205 let background_color = opt_lit(self.background_color.as_ref());
3206 let background_throttling = opt_lit(self.background_throttling.as_ref());
3207 let javascript_disabled = self.javascript_disabled;
3208 let allow_link_preview = self.allow_link_preview;
3209 let disable_input_accessory_view = self.disable_input_accessory_view;
3210
3211 literal_struct!(
3212 tokens,
3213 ::tauri::utils::config::WindowConfig,
3214 label,
3215 url,
3216 create,
3217 user_agent,
3218 drag_drop_enabled,
3219 center,
3220 x,
3221 y,
3222 width,
3223 height,
3224 min_width,
3225 min_height,
3226 max_width,
3227 max_height,
3228 prevent_overflow,
3229 resizable,
3230 maximizable,
3231 minimizable,
3232 closable,
3233 title,
3234 proxy_url,
3235 fullscreen,
3236 focus,
3237 transparent,
3238 maximized,
3239 visible,
3240 decorations,
3241 always_on_bottom,
3242 always_on_top,
3243 visible_on_all_workspaces,
3244 content_protected,
3245 skip_taskbar,
3246 window_classname,
3247 theme,
3248 title_bar_style,
3249 traffic_light_position,
3250 hidden_title,
3251 accept_first_mouse,
3252 tabbing_identifier,
3253 additional_browser_args,
3254 shadow,
3255 window_effects,
3256 incognito,
3257 parent,
3258 zoom_hotkeys_enabled,
3259 browser_extensions_enabled,
3260 use_https_scheme,
3261 devtools,
3262 background_color,
3263 background_throttling,
3264 javascript_disabled,
3265 allow_link_preview,
3266 disable_input_accessory_view
3267 );
3268 }
3269 }
3270
3271 impl ToTokens for PatternKind {
3272 fn to_tokens(&self, tokens: &mut TokenStream) {
3273 let prefix = quote! { ::tauri::utils::config::PatternKind };
3274
3275 tokens.append_all(match self {
3276 Self::Brownfield => quote! { #prefix::Brownfield },
3277 #[cfg(not(feature = "isolation"))]
3278 Self::Isolation { dir: _ } => quote! { #prefix::Brownfield },
3279 #[cfg(feature = "isolation")]
3280 Self::Isolation { dir } => {
3281 let dir = path_buf_lit(dir);
3282 quote! { #prefix::Isolation { dir: #dir } }
3283 }
3284 })
3285 }
3286 }
3287
3288 impl ToTokens for WebviewInstallMode {
3289 fn to_tokens(&self, tokens: &mut TokenStream) {
3290 let prefix = quote! { ::tauri::utils::config::WebviewInstallMode };
3291
3292 tokens.append_all(match self {
3293 Self::Skip => quote! { #prefix::Skip },
3294 Self::DownloadBootstrapper { silent } => {
3295 quote! { #prefix::DownloadBootstrapper { silent: #silent } }
3296 }
3297 Self::EmbedBootstrapper { silent } => {
3298 quote! { #prefix::EmbedBootstrapper { silent: #silent } }
3299 }
3300 Self::OfflineInstaller { silent } => {
3301 quote! { #prefix::OfflineInstaller { silent: #silent } }
3302 }
3303 Self::FixedRuntime { path } => {
3304 let path = path_buf_lit(path);
3305 quote! { #prefix::FixedRuntime { path: #path } }
3306 }
3307 })
3308 }
3309 }
3310
3311 impl ToTokens for WindowsConfig {
3312 fn to_tokens(&self, tokens: &mut TokenStream) {
3313 let webview_install_mode = &self.webview_install_mode;
3314 tokens.append_all(quote! { ::tauri::utils::config::WindowsConfig {
3315 webview_install_mode: #webview_install_mode,
3316 ..Default::default()
3317 }})
3318 }
3319 }
3320
3321 impl ToTokens for BundleConfig {
3322 fn to_tokens(&self, tokens: &mut TokenStream) {
3323 let publisher = quote!(None);
3324 let homepage = quote!(None);
3325 let icon = vec_lit(&self.icon, str_lit);
3326 let active = self.active;
3327 let targets = quote!(Default::default());
3328 let create_updater_artifacts = quote!(Default::default());
3329 let resources = quote!(None);
3330 let copyright = quote!(None);
3331 let category = quote!(None);
3332 let file_associations = quote!(None);
3333 let short_description = quote!(None);
3334 let long_description = quote!(None);
3335 let use_local_tools_dir = self.use_local_tools_dir;
3336 let external_bin = opt_vec_lit(self.external_bin.as_ref(), str_lit);
3337 let windows = &self.windows;
3338 let license = opt_str_lit(self.license.as_ref());
3339 let license_file = opt_lit(self.license_file.as_ref().map(path_buf_lit).as_ref());
3340 let linux = quote!(Default::default());
3341 let macos = quote!(Default::default());
3342 let ios = quote!(Default::default());
3343 let android = quote!(Default::default());
3344
3345 literal_struct!(
3346 tokens,
3347 ::tauri::utils::config::BundleConfig,
3348 active,
3349 publisher,
3350 homepage,
3351 icon,
3352 targets,
3353 create_updater_artifacts,
3354 resources,
3355 copyright,
3356 category,
3357 license,
3358 license_file,
3359 file_associations,
3360 short_description,
3361 long_description,
3362 use_local_tools_dir,
3363 external_bin,
3364 windows,
3365 linux,
3366 macos,
3367 ios,
3368 android
3369 );
3370 }
3371 }
3372
3373 impl ToTokens for FrontendDist {
3374 fn to_tokens(&self, tokens: &mut TokenStream) {
3375 let prefix = quote! { ::tauri::utils::config::FrontendDist };
3376
3377 tokens.append_all(match self {
3378 Self::Url(url) => {
3379 let url = url_lit(url);
3380 quote! { #prefix::Url(#url) }
3381 }
3382 Self::Directory(path) => {
3383 let path = path_buf_lit(path);
3384 quote! { #prefix::Directory(#path) }
3385 }
3386 Self::Files(files) => {
3387 let files = vec_lit(files, path_buf_lit);
3388 quote! { #prefix::Files(#files) }
3389 }
3390 })
3391 }
3392 }
3393
3394 impl ToTokens for BuildConfig {
3395 fn to_tokens(&self, tokens: &mut TokenStream) {
3396 let dev_url = opt_lit(self.dev_url.as_ref().map(url_lit).as_ref());
3397 let frontend_dist = opt_lit(self.frontend_dist.as_ref());
3398 let runner = quote!(None);
3399 let before_dev_command = quote!(None);
3400 let before_build_command = quote!(None);
3401 let before_bundle_command = quote!(None);
3402 let features = quote!(None);
3403 let remove_unused_commands = quote!(false);
3404
3405 literal_struct!(
3406 tokens,
3407 ::tauri::utils::config::BuildConfig,
3408 runner,
3409 dev_url,
3410 frontend_dist,
3411 before_dev_command,
3412 before_build_command,
3413 before_bundle_command,
3414 features,
3415 remove_unused_commands
3416 );
3417 }
3418 }
3419
3420 impl ToTokens for CspDirectiveSources {
3421 fn to_tokens(&self, tokens: &mut TokenStream) {
3422 let prefix = quote! { ::tauri::utils::config::CspDirectiveSources };
3423
3424 tokens.append_all(match self {
3425 Self::Inline(sources) => {
3426 let sources = sources.as_str();
3427 quote!(#prefix::Inline(#sources.into()))
3428 }
3429 Self::List(list) => {
3430 let list = vec_lit(list, str_lit);
3431 quote!(#prefix::List(#list))
3432 }
3433 })
3434 }
3435 }
3436
3437 impl ToTokens for Csp {
3438 fn to_tokens(&self, tokens: &mut TokenStream) {
3439 let prefix = quote! { ::tauri::utils::config::Csp };
3440
3441 tokens.append_all(match self {
3442 Self::Policy(policy) => {
3443 let policy = policy.as_str();
3444 quote!(#prefix::Policy(#policy.into()))
3445 }
3446 Self::DirectiveMap(list) => {
3447 let map = map_lit(
3448 quote! { ::std::collections::HashMap },
3449 list,
3450 str_lit,
3451 identity,
3452 );
3453 quote!(#prefix::DirectiveMap(#map))
3454 }
3455 })
3456 }
3457 }
3458
3459 impl ToTokens for DisabledCspModificationKind {
3460 fn to_tokens(&self, tokens: &mut TokenStream) {
3461 let prefix = quote! { ::tauri::utils::config::DisabledCspModificationKind };
3462
3463 tokens.append_all(match self {
3464 Self::Flag(flag) => {
3465 quote! { #prefix::Flag(#flag) }
3466 }
3467 Self::List(directives) => {
3468 let directives = vec_lit(directives, str_lit);
3469 quote! { #prefix::List(#directives) }
3470 }
3471 });
3472 }
3473 }
3474
3475 impl ToTokens for CapabilityEntry {
3476 fn to_tokens(&self, tokens: &mut TokenStream) {
3477 let prefix = quote! { ::tauri::utils::config::CapabilityEntry };
3478
3479 tokens.append_all(match self {
3480 Self::Inlined(capability) => {
3481 quote! { #prefix::Inlined(#capability) }
3482 }
3483 Self::Reference(id) => {
3484 let id = str_lit(id);
3485 quote! { #prefix::Reference(#id) }
3486 }
3487 });
3488 }
3489 }
3490
3491 impl ToTokens for HeaderSource {
3492 fn to_tokens(&self, tokens: &mut TokenStream) {
3493 let prefix = quote! { ::tauri::utils::config::HeaderSource };
3494
3495 tokens.append_all(match self {
3496 Self::Inline(s) => {
3497 let line = s.as_str();
3498 quote!(#prefix::Inline(#line.into()))
3499 }
3500 Self::List(l) => {
3501 let list = vec_lit(l, str_lit);
3502 quote!(#prefix::List(#list))
3503 }
3504 Self::Map(m) => {
3505 let map = map_lit(quote! { ::std::collections::HashMap }, m, str_lit, str_lit);
3506 quote!(#prefix::Map(#map))
3507 }
3508 })
3509 }
3510 }
3511
3512 impl ToTokens for HeaderConfig {
3513 fn to_tokens(&self, tokens: &mut TokenStream) {
3514 let access_control_allow_credentials =
3515 opt_lit(self.access_control_allow_credentials.as_ref());
3516 let access_control_allow_headers = opt_lit(self.access_control_allow_headers.as_ref());
3517 let access_control_allow_methods = opt_lit(self.access_control_allow_methods.as_ref());
3518 let access_control_expose_headers = opt_lit(self.access_control_expose_headers.as_ref());
3519 let access_control_max_age = opt_lit(self.access_control_max_age.as_ref());
3520 let cross_origin_embedder_policy = opt_lit(self.cross_origin_embedder_policy.as_ref());
3521 let cross_origin_opener_policy = opt_lit(self.cross_origin_opener_policy.as_ref());
3522 let cross_origin_resource_policy = opt_lit(self.cross_origin_resource_policy.as_ref());
3523 let permissions_policy = opt_lit(self.permissions_policy.as_ref());
3524 let timing_allow_origin = opt_lit(self.timing_allow_origin.as_ref());
3525 let x_content_type_options = opt_lit(self.x_content_type_options.as_ref());
3526 let tauri_custom_header = opt_lit(self.tauri_custom_header.as_ref());
3527
3528 literal_struct!(
3529 tokens,
3530 ::tauri::utils::config::HeaderConfig,
3531 access_control_allow_credentials,
3532 access_control_allow_headers,
3533 access_control_allow_methods,
3534 access_control_expose_headers,
3535 access_control_max_age,
3536 cross_origin_embedder_policy,
3537 cross_origin_opener_policy,
3538 cross_origin_resource_policy,
3539 permissions_policy,
3540 timing_allow_origin,
3541 x_content_type_options,
3542 tauri_custom_header
3543 );
3544 }
3545 }
3546
3547 impl ToTokens for SecurityConfig {
3548 fn to_tokens(&self, tokens: &mut TokenStream) {
3549 let csp = opt_lit(self.csp.as_ref());
3550 let dev_csp = opt_lit(self.dev_csp.as_ref());
3551 let freeze_prototype = self.freeze_prototype;
3552 let dangerous_disable_asset_csp_modification = &self.dangerous_disable_asset_csp_modification;
3553 let asset_protocol = &self.asset_protocol;
3554 let pattern = &self.pattern;
3555 let capabilities = vec_lit(&self.capabilities, identity);
3556 let headers = opt_lit(self.headers.as_ref());
3557
3558 literal_struct!(
3559 tokens,
3560 ::tauri::utils::config::SecurityConfig,
3561 csp,
3562 dev_csp,
3563 freeze_prototype,
3564 dangerous_disable_asset_csp_modification,
3565 asset_protocol,
3566 pattern,
3567 capabilities,
3568 headers
3569 );
3570 }
3571 }
3572
3573 impl ToTokens for TrayIconConfig {
3574 fn to_tokens(&self, tokens: &mut TokenStream) {
3575 tokens.append_all(quote!(#[allow(deprecated)]));
3577
3578 let id = opt_str_lit(self.id.as_ref());
3579 let icon_as_template = self.icon_as_template;
3580 #[allow(deprecated)]
3581 let menu_on_left_click = self.menu_on_left_click;
3582 let show_menu_on_left_click = self.show_menu_on_left_click;
3583 let icon_path = path_buf_lit(&self.icon_path);
3584 let title = opt_str_lit(self.title.as_ref());
3585 let tooltip = opt_str_lit(self.tooltip.as_ref());
3586 literal_struct!(
3587 tokens,
3588 ::tauri::utils::config::TrayIconConfig,
3589 id,
3590 icon_path,
3591 icon_as_template,
3592 menu_on_left_click,
3593 show_menu_on_left_click,
3594 title,
3595 tooltip
3596 );
3597 }
3598 }
3599
3600 impl ToTokens for FsScope {
3601 fn to_tokens(&self, tokens: &mut TokenStream) {
3602 let prefix = quote! { ::tauri::utils::config::FsScope };
3603
3604 tokens.append_all(match self {
3605 Self::AllowedPaths(allow) => {
3606 let allowed_paths = vec_lit(allow, path_buf_lit);
3607 quote! { #prefix::AllowedPaths(#allowed_paths) }
3608 }
3609 Self::Scope { allow, deny , require_literal_leading_dot} => {
3610 let allow = vec_lit(allow, path_buf_lit);
3611 let deny = vec_lit(deny, path_buf_lit);
3612 let require_literal_leading_dot = opt_lit(require_literal_leading_dot.as_ref());
3613 quote! { #prefix::Scope { allow: #allow, deny: #deny, require_literal_leading_dot: #require_literal_leading_dot } }
3614 }
3615 });
3616 }
3617 }
3618
3619 impl ToTokens for AssetProtocolConfig {
3620 fn to_tokens(&self, tokens: &mut TokenStream) {
3621 let scope = &self.scope;
3622 tokens.append_all(quote! { ::tauri::utils::config::AssetProtocolConfig { scope: #scope, ..Default::default() } })
3623 }
3624 }
3625
3626 impl ToTokens for AppConfig {
3627 fn to_tokens(&self, tokens: &mut TokenStream) {
3628 let windows = vec_lit(&self.windows, identity);
3629 let security = &self.security;
3630 let tray_icon = opt_lit(self.tray_icon.as_ref());
3631 let macos_private_api = self.macos_private_api;
3632 let with_global_tauri = self.with_global_tauri;
3633 let enable_gtk_app_id = self.enable_gtk_app_id;
3634
3635 literal_struct!(
3636 tokens,
3637 ::tauri::utils::config::AppConfig,
3638 windows,
3639 security,
3640 tray_icon,
3641 macos_private_api,
3642 with_global_tauri,
3643 enable_gtk_app_id
3644 );
3645 }
3646 }
3647
3648 impl ToTokens for PluginConfig {
3649 fn to_tokens(&self, tokens: &mut TokenStream) {
3650 let config = map_lit(
3651 quote! { ::std::collections::HashMap },
3652 &self.0,
3653 str_lit,
3654 json_value_lit,
3655 );
3656 tokens.append_all(quote! { ::tauri::utils::config::PluginConfig(#config) })
3657 }
3658 }
3659
3660 impl ToTokens for Config {
3661 fn to_tokens(&self, tokens: &mut TokenStream) {
3662 let schema = quote!(None);
3663 let product_name = opt_str_lit(self.product_name.as_ref());
3664 let main_binary_name = opt_str_lit(self.main_binary_name.as_ref());
3665 let version = opt_str_lit(self.version.as_ref());
3666 let identifier = str_lit(&self.identifier);
3667 let app = &self.app;
3668 let build = &self.build;
3669 let bundle = &self.bundle;
3670 let plugins = &self.plugins;
3671
3672 literal_struct!(
3673 tokens,
3674 ::tauri::utils::config::Config,
3675 schema,
3676 product_name,
3677 main_binary_name,
3678 version,
3679 identifier,
3680 app,
3681 build,
3682 bundle,
3683 plugins
3684 );
3685 }
3686 }
3687}
3688
3689#[cfg(test)]
3690mod test {
3691 use super::*;
3692
3693 #[test]
3696 fn test_defaults() {
3698 let a_config = AppConfig::default();
3700 let b_config = BuildConfig::default();
3702 let d_windows: Vec<WindowConfig> = vec![];
3704 let d_bundle = BundleConfig::default();
3706
3707 let app = AppConfig {
3709 windows: vec![],
3710 security: SecurityConfig {
3711 csp: None,
3712 dev_csp: None,
3713 freeze_prototype: false,
3714 dangerous_disable_asset_csp_modification: DisabledCspModificationKind::Flag(false),
3715 asset_protocol: AssetProtocolConfig::default(),
3716 pattern: Default::default(),
3717 capabilities: Vec::new(),
3718 headers: None,
3719 },
3720 tray_icon: None,
3721 macos_private_api: false,
3722 with_global_tauri: false,
3723 enable_gtk_app_id: false,
3724 };
3725
3726 let build = BuildConfig {
3728 runner: None,
3729 dev_url: None,
3730 frontend_dist: None,
3731 before_dev_command: None,
3732 before_build_command: None,
3733 before_bundle_command: None,
3734 features: None,
3735 remove_unused_commands: false,
3736 };
3737
3738 let bundle = BundleConfig {
3740 active: false,
3741 targets: Default::default(),
3742 create_updater_artifacts: Default::default(),
3743 publisher: None,
3744 homepage: None,
3745 icon: Vec::new(),
3746 resources: None,
3747 copyright: None,
3748 category: None,
3749 file_associations: None,
3750 short_description: None,
3751 long_description: None,
3752 use_local_tools_dir: false,
3753 license: None,
3754 license_file: None,
3755 linux: Default::default(),
3756 macos: Default::default(),
3757 external_bin: None,
3758 windows: Default::default(),
3759 ios: Default::default(),
3760 android: Default::default(),
3761 };
3762
3763 assert_eq!(a_config, app);
3765 assert_eq!(b_config, build);
3766 assert_eq!(d_bundle, bundle);
3767 assert_eq!(d_windows, app.windows);
3768 }
3769
3770 #[test]
3771 fn parse_hex_color() {
3772 use super::Color;
3773
3774 assert_eq!(Color(255, 255, 255, 255), "fff".parse().unwrap());
3775 assert_eq!(Color(255, 255, 255, 255), "#fff".parse().unwrap());
3776 assert_eq!(Color(0, 0, 0, 255), "#000000".parse().unwrap());
3777 assert_eq!(Color(0, 0, 0, 255), "#000000ff".parse().unwrap());
3778 assert_eq!(Color(0, 255, 0, 255), "#00ff00ff".parse().unwrap());
3779 }
3780}