1use std::fmt::{Display, Formatter};
56use std::ops::RangeBounds;
57use std::{env, fmt};
58
59use crate::manifest::xml::XmlFormatter;
60
61mod xml;
62
63#[cfg(test)]
64mod test;
65
66#[derive(Debug)]
70pub struct ManifestBuilder {
71 identity: Option<AssemblyIdentity>,
72 dependent_assemblies: Vec<AssemblyIdentity>,
73 compatibility: ApplicationCompatibility,
74 windows_settings: WindowsSettings,
75 requested_execution_level: Option<RequestedExecutionLevel>,
76}
77
78impl ManifestBuilder {
79 pub(crate) fn new(name: &str) -> Self {
80 ManifestBuilder {
81 identity: Some(AssemblyIdentity::application(name)),
82 dependent_assemblies: vec![AssemblyIdentity::new(
83 "Microsoft.Windows.Common-Controls",
84 [6, 0, 0, 0],
85 0x6595b64144ccf1df,
86 )],
87 compatibility: ApplicationCompatibility {
88 max_version_tested: Some(MaxVersionTested::Windows10Version1903),
89 supported_os: vec![
90 SupportedOS::Windows7,
91 SupportedOS::Windows8,
92 SupportedOS::Windows81,
93 SupportedOS::Windows10,
94 ],
95 },
96 windows_settings: WindowsSettings::new(),
97 requested_execution_level: Some(RequestedExecutionLevel {
98 level: ExecutionLevel::AsInvoker,
99 ui_access: false,
100 }),
101 }
102 }
103
104 pub(crate) fn empty() -> Self {
105 ManifestBuilder {
106 identity: None,
107 dependent_assemblies: Vec::new(),
108 compatibility: ApplicationCompatibility {
109 max_version_tested: None,
110 supported_os: Vec::new(),
111 },
112 windows_settings: WindowsSettings::empty(),
113 requested_execution_level: None,
114 }
115 }
116
117 pub fn name(mut self, name: &str) -> Self {
121 match self.identity {
122 Some(ref mut identity) => identity.name = name.to_string(),
123 None => self.identity = Some(AssemblyIdentity::application_version(name, 0, 0, 0, 0)),
124 }
125 self
126 }
127
128 pub fn version(mut self, major: u16, minor: u16, build: u16, revision: u16) -> Self {
130 match self.identity {
131 Some(ref mut identity) => identity.version = Version(major, minor, build, revision),
132 None => {
133 self.identity = Some(AssemblyIdentity::application_version("", major, minor, build, revision));
134 }
135 }
136 self
137 }
138
139 pub fn dependency(mut self, identity: AssemblyIdentity) -> Self {
145 self.dependent_assemblies.push(identity);
146 self
147 }
148
149 pub fn remove_dependency(mut self, name: &str) -> Self {
159 self.dependent_assemblies.retain(|d| d.name != name);
160 self
161 }
162
163 pub fn max_version_tested(mut self, version: MaxVersionTested) -> Self {
169 self.compatibility.max_version_tested = Some(version);
170 self
171 }
172
173 pub fn remove_max_version_tested(mut self) -> Self {
175 self.compatibility.max_version_tested = None;
176 self
177 }
178
179 pub fn supported_os<R: RangeBounds<SupportedOS>>(mut self, os_range: R) -> Self {
183 use SupportedOS::*;
184
185 self.compatibility.supported_os.clear();
186 for os in [WindowsVista, Windows7, Windows8, Windows81, Windows10] {
187 if os_range.contains(&os) {
188 self.compatibility.supported_os.push(os);
189 }
190 }
191 self
192 }
193
194 pub fn active_code_page(mut self, code_page: ActiveCodePage) -> Self {
198 self.windows_settings.active_code_page = code_page;
199 self
200 }
201
202 pub fn dpi_awareness(mut self, setting: DpiAwareness) -> Self {
207 self.windows_settings.dpi_awareness = setting;
208 self
209 }
210
211 pub fn gdi_scaling(mut self, setting: Setting) -> Self {
214 self.windows_settings.gdi_scaling = setting.enabled();
215 self
216 }
217
218 pub fn heap_type(mut self, setting: HeapType) -> Self {
223 self.windows_settings.heap_type = setting;
224 self
225 }
226
227 pub fn long_path_aware(mut self, setting: Setting) -> Self {
238 self.windows_settings.long_path_aware = setting.enabled();
239 self
240 }
241
242 pub fn printer_driver_isolation(mut self, setting: Setting) -> Self {
249 self.windows_settings.printer_driver_isolation = setting.enabled();
250 self
251 }
252
253 pub fn scrolling_awareness(mut self, setting: ScrollingAwareness) -> Self {
259 self.windows_settings.scrolling_awareness = setting;
260 self
261 }
262
263 pub fn window_filtering(mut self, setting: Setting) -> Self {
266 self.windows_settings.disable_window_filtering = setting.disabled();
267 self
268 }
269
270 pub fn requested_execution_level(mut self, level: ExecutionLevel) -> Self {
276 match self.requested_execution_level {
277 Some(ref mut requested_execution_level) => requested_execution_level.level = level,
278 None => self.requested_execution_level = Some(RequestedExecutionLevel { level, ui_access: false }),
279 }
280 self
281 }
282
283 pub fn ui_access(mut self, access: bool) -> Self {
288 match self.requested_execution_level {
289 Some(ref mut requested_execution_level) => requested_execution_level.ui_access = access,
290 None => {
291 self.requested_execution_level = Some(RequestedExecutionLevel {
292 level: ExecutionLevel::AsInvoker,
293 ui_access: access,
294 })
295 }
296 }
297 self
298 }
299}
300
301impl Display for ManifestBuilder {
302 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
303 let mut w = XmlFormatter::new(f);
304 w.start_document()?;
305 let mut attrs = vec![("xmlns", "urn:schemas-microsoft-com:asm.v1")];
306 if !self.windows_settings.is_empty() || self.requested_execution_level.is_some() {
307 attrs.push(("xmlns:asmv3", "urn:schemas-microsoft-com:asm.v3"));
308 }
309 attrs.push(("manifestVersion", "1.0"));
310 w.start_element("assembly", &attrs)?;
311 if let Some(ref identity) = self.identity {
312 identity.xml_to(&mut w)?;
313 }
314 if !self.dependent_assemblies.is_empty() {
315 w.element("dependency", &[], |w| {
316 for d in self.dependent_assemblies.as_slice() {
317 w.element("dependentAssembly", &[], |w| d.xml_to(w))?;
318 }
319 Ok(())
320 })?;
321 }
322 if !self.compatibility.is_empty() {
323 self.compatibility.xml_to(&mut w)?;
324 }
325 if !self.windows_settings.is_empty() {
326 self.windows_settings.xml_to(&mut w)?;
327 }
328 if let Some(ref requested_execution_level) = self.requested_execution_level {
329 requested_execution_level.xml_to(&mut w)?;
330 }
331 w.end_element("assembly")
332 }
333}
334
335#[derive(Debug)]
337pub struct AssemblyIdentity {
338 r#type: AssemblyType,
339 name: String,
340 language: Option<String>,
341 processor_architecture: Option<AssemblyProcessorArchitecture>,
342 version: Version,
343 public_key_token: Option<PublicKeyToken>,
344}
345
346impl AssemblyIdentity {
347 fn application(name: &str) -> AssemblyIdentity {
348 let major = env::var("CARGO_PKG_VERSION_MAJOR").map_or(0, |s| s.parse().unwrap_or(0));
349 let minor = env::var("CARGO_PKG_VERSION_MINOR").map_or(0, |s| s.parse().unwrap_or(0));
350 let patch = env::var("CARGO_PKG_VERSION_PATCH").map_or(0, |s| s.parse().unwrap_or(0));
351 AssemblyIdentity {
352 r#type: AssemblyType::Win32,
353 name: name.to_string(),
354 language: None,
355 processor_architecture: None,
356 version: Version(major, minor, patch, 0),
357 public_key_token: None,
358 }
359 }
360
361 fn application_version(name: &str, major: u16, minor: u16, build: u16, revision: u16) -> AssemblyIdentity {
362 AssemblyIdentity {
363 r#type: AssemblyType::Win32,
364 name: name.to_string(),
365 language: None,
366 processor_architecture: None,
367 version: Version(major, minor, build, revision),
368 public_key_token: None,
369 }
370 }
371
372 pub fn new(name: &str, version: [u16; 4], public_key_token: u64) -> AssemblyIdentity {
376 AssemblyIdentity {
377 r#type: AssemblyType::Win32,
378 name: name.to_string(),
379 language: Some("*".to_string()),
380 processor_architecture: Some(AssemblyProcessorArchitecture::All),
381 version: Version(version[0], version[1], version[2], version[3]),
382 public_key_token: Some(PublicKeyToken(public_key_token)),
383 }
384 }
385
386 pub fn language(mut self, language: &str) -> Self {
388 self.language = Some(language.to_string());
389 self
390 }
391
392 pub fn processor_architecture(mut self, arch: AssemblyProcessorArchitecture) -> Self {
394 self.processor_architecture = Some(arch);
395 self
396 }
397
398 fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
399 let version = self.version.to_string();
400 let public_key_token = self.public_key_token.as_ref().map(|token| token.to_string());
401
402 let mut attrs: Vec<(&str, &str)> = Vec::with_capacity(6);
403 if let Some(ref language) = self.language {
404 attrs.push(("language", language));
405 }
406 attrs.push(("name", &self.name));
407 if let Some(ref arch) = self.processor_architecture {
408 attrs.push(("processorArchitecture", arch.as_str()))
409 }
410 if let Some(ref token) = public_key_token {
411 attrs.push(("publicKeyToken", token));
412 }
413 attrs.push(("type", self.r#type.as_str()));
414 attrs.push(("version", &version));
415 w.empty_element("assemblyIdentity", &attrs)
416 }
417}
418
419#[derive(Debug)]
420struct Version(u16, u16, u16, u16);
421
422impl fmt::Display for Version {
423 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
424 write!(f, "{}.{}.{}.{}", self.0, self.1, self.2, self.3)
425 }
426}
427
428#[derive(Debug)]
429struct PublicKeyToken(u64);
430
431impl fmt::Display for PublicKeyToken {
432 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
433 write!(f, "{:016x}", self.0)
434 }
435}
436
437#[derive(Debug)]
439#[non_exhaustive]
440pub enum AssemblyProcessorArchitecture {
441 All,
443 X86,
445 Amd64,
447 Arm,
449 Arm64,
451}
452
453impl AssemblyProcessorArchitecture {
454 pub fn as_str(&self) -> &'static str {
455 match self {
456 Self::All => "*",
457 Self::X86 => "x86",
458 Self::Amd64 => "amd64",
459 Self::Arm => "arm",
460 Self::Arm64 => "arm64",
461 }
462 }
463}
464
465impl fmt::Display for AssemblyProcessorArchitecture {
466 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
467 f.pad(self.as_str())
468 }
469}
470
471#[derive(Debug)]
472#[non_exhaustive]
473enum AssemblyType {
474 Win32,
475}
476
477impl AssemblyType {
478 fn as_str(&self) -> &'static str {
479 "win32"
480 }
481}
482
483#[derive(Debug)]
484struct ApplicationCompatibility {
485 max_version_tested: Option<MaxVersionTested>,
486 supported_os: Vec<SupportedOS>,
487}
488
489impl ApplicationCompatibility {
490 fn is_empty(&self) -> bool {
491 self.supported_os.is_empty()
492 }
493
494 fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
495 w.element(
496 "compatibility",
497 &[("xmlns", "urn:schemas-microsoft-com:compatibility.v1")],
498 |w| {
499 w.element("application", &[], |w| {
500 if self.supported_os.contains(&SupportedOS::Windows10) {
501 if let Some(ref version) = self.max_version_tested {
502 w.empty_element("maxversiontested", &[("Id", version.as_str())])?;
503 }
504 }
505 for os in self.supported_os.iter() {
506 w.empty_element("supportedOS", &[("Id", os.as_str())])?
507 }
508 Ok(())
509 })
510 },
511 )
512 }
513}
514
515#[derive(Debug)]
518#[non_exhaustive]
519pub enum MaxVersionTested {
520 Windows10Version1903,
522 Windows10Version2004,
524 Windows10Version2104,
526 Windows11,
528 Windows11Version22H2,
530}
531
532impl MaxVersionTested {
533 pub fn as_str(&self) -> &'static str {
535 match self {
536 Self::Windows10Version1903 => "10.0.18362.1",
537 Self::Windows10Version2004 => "10.0.19041.0",
538 Self::Windows10Version2104 => "10.0.20348.0",
539 Self::Windows11 => "10.0.22000.194",
540 Self::Windows11Version22H2 => "10.0.22621.1",
541 }
542 }
543}
544
545impl Display for MaxVersionTested {
546 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
547 f.pad(self.as_str())
548 }
549}
550
551#[derive(Debug, Ord, PartialOrd, Eq, PartialEq)]
553#[non_exhaustive]
554pub enum SupportedOS {
555 WindowsVista,
557 Windows7,
559 Windows8,
561 Windows81,
563 Windows10,
565}
566
567impl SupportedOS {
568 pub fn as_str(&self) -> &'static str {
570 match self {
571 Self::WindowsVista => "{e2011457-1546-43c5-a5fe-008deee3d3f0}",
572 Self::Windows7 => "{35138b9a-5d96-4fbd-8e2d-a2440225f93a}",
573 Self::Windows8 => "{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}",
574 Self::Windows81 => "{1f676c76-80e1-4239-95bb-83d0f6d0da78}",
575 Self::Windows10 => "{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}",
576 }
577 }
578}
579
580impl Display for SupportedOS {
581 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
582 f.pad(self.as_str())
583 }
584}
585
586static WS2005: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2005/WindowsSettings");
587static WS2011: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2011/WindowsSettings");
588static WS2013: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2013/WindowsSettings");
589static WS2016: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2016/WindowsSettings");
590static WS2017: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2017/WindowsSettings");
591static WS2019: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2019/WindowsSettings");
592static WS2020: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2020/WindowsSettings");
593
594#[derive(Debug)]
595struct WindowsSettings {
596 active_code_page: ActiveCodePage,
597 disable_window_filtering: bool,
598 dpi_awareness: DpiAwareness,
599 gdi_scaling: bool,
600 heap_type: HeapType,
601 long_path_aware: bool,
602 printer_driver_isolation: bool,
603 scrolling_awareness: ScrollingAwareness,
604}
605
606impl WindowsSettings {
607 fn new() -> Self {
608 Self {
609 active_code_page: ActiveCodePage::Utf8,
610 disable_window_filtering: false,
611 dpi_awareness: DpiAwareness::PerMonitorV2Only,
612 gdi_scaling: false,
613 heap_type: HeapType::LowFragmentationHeap,
614 long_path_aware: true,
615 printer_driver_isolation: true,
616 scrolling_awareness: ScrollingAwareness::UltraHighResolution,
617 }
618 }
619
620 fn empty() -> Self {
621 Self {
622 active_code_page: ActiveCodePage::System,
623 disable_window_filtering: false,
624 dpi_awareness: DpiAwareness::UnawareByDefault,
625 gdi_scaling: false,
626 heap_type: HeapType::LowFragmentationHeap,
627 long_path_aware: false,
628 printer_driver_isolation: false,
629 scrolling_awareness: ScrollingAwareness::UltraHighResolution,
630 }
631 }
632
633 fn is_empty(&self) -> bool {
634 matches!(
635 self,
636 Self {
637 active_code_page: ActiveCodePage::System,
638 disable_window_filtering: false,
639 dpi_awareness: DpiAwareness::UnawareByDefault,
640 gdi_scaling: false,
641 heap_type: HeapType::LowFragmentationHeap,
642 long_path_aware: false,
643 printer_driver_isolation: false,
644 scrolling_awareness: ScrollingAwareness::UltraHighResolution,
645 }
646 )
647 }
648
649 fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
650 w.element("asmv3:application", &[], |w| {
651 w.element("asmv3:windowsSettings", &[], |w| {
652 self.active_code_page.xml_to(w)?;
653 if self.disable_window_filtering {
654 w.element("disableWindowFiltering", &[WS2011], |w| w.text("true"))?;
655 }
656 self.dpi_awareness.xml_to(w)?;
657 if self.gdi_scaling {
658 w.element("gdiScaling", &[WS2017], |w| w.text("true"))?;
659 }
660 if matches!(self.heap_type, HeapType::SegmentHeap) {
661 w.element("heapType", &[WS2020], |w| w.text("SegmentHeap"))?;
662 }
663 if self.long_path_aware {
664 w.element("longPathAware", &[WS2016], |w| w.text("true"))?;
665 }
666 if self.printer_driver_isolation {
667 w.element("printerDriverIsolation", &[WS2011], |w| w.text("true"))?;
668 }
669 self.scrolling_awareness.xml_to(w)
670 })
671 })
672 }
673}
674
675#[derive(Debug)]
678pub enum Setting {
679 Disabled = 0,
680 Enabled = 1,
681}
682
683impl Setting {
684 fn disabled(&self) -> bool {
686 matches!(self, Setting::Disabled)
687 }
688
689 fn enabled(&self) -> bool {
691 matches!(self, Setting::Enabled)
692 }
693}
694
695#[derive(Debug)]
697#[non_exhaustive]
698pub enum ActiveCodePage {
699 System,
702 Utf8,
704 Legacy,
707 Locale(String),
710}
711
712impl ActiveCodePage {
713 pub fn as_str(&self) -> &str {
714 match self {
715 Self::System => "",
716 Self::Utf8 => "UTF-8",
717 Self::Legacy => "Legacy",
718 Self::Locale(s) => s,
719 }
720 }
721
722 fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
723 match self {
724 Self::System => Ok(()),
725 _ => w.element("activeCodePage", &[WS2019], |w| w.text(self.as_str())),
726 }
727 }
728}
729
730impl Display for ActiveCodePage {
731 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
732 f.pad(self.as_str())
733 }
734}
735
736#[derive(Debug)]
744#[non_exhaustive]
745pub enum DpiAwareness {
746 UnawareByDefault,
749 Unaware,
752 System,
756 PerMonitor,
760 PerMonitorV2,
764 PerMonitorV2Only,
768}
769
770impl DpiAwareness {
771 fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
772 let settings = match self {
773 Self::UnawareByDefault => (None, None),
774 Self::Unaware => (Some("false"), None),
775 Self::System => (Some("true"), None),
776 Self::PerMonitor => (Some("true/pm"), None),
777 Self::PerMonitorV2 => (Some("true/pm"), Some("permonitorv2,permonitor")),
778 Self::PerMonitorV2Only => (None, Some("permonitorv2")),
779 };
780 if let Some(dpi_aware) = settings.0 {
781 w.element("dpiAware", &[WS2005], |w| w.text(dpi_aware))?;
782 }
783 if let Some(dpi_awareness) = settings.1 {
784 w.element("dpiAwareness", &[WS2016], |w| w.text(dpi_awareness))?;
785 }
786 Ok(())
787 }
788}
789
790#[derive(Debug)]
792#[non_exhaustive]
793pub enum HeapType {
794 LowFragmentationHeap,
796 SegmentHeap,
802}
803
804#[derive(Debug)]
807#[non_exhaustive]
808pub enum ScrollingAwareness {
809 LowResolution,
811 HighResolution,
813 UltraHighResolution,
815}
816
817impl ScrollingAwareness {
818 fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
819 match self {
820 Self::LowResolution => w.element("ultraHighResolutionScrollingAware", &[WS2013], |w| w.text("false")),
821 Self::HighResolution => w.element("highResolutionScrollingAware", &[WS2013], |w| w.text("true")),
822 Self::UltraHighResolution => Ok(()),
823 }
824 }
825}
826
827#[derive(Debug)]
828struct RequestedExecutionLevel {
829 level: ExecutionLevel,
830 ui_access: bool,
831}
832
833impl RequestedExecutionLevel {
834 fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
835 w.element("asmv3:trustInfo", &[], |w| {
836 w.element("asmv3:security", &[], |w| {
837 w.element("asmv3:requestedPrivileges", &[], |w| {
838 w.empty_element(
839 "asmv3:requestedExecutionLevel",
840 &[
841 ("level", self.level.as_str()),
842 ("uiAccess", if self.ui_access { "true" } else { "false" }),
843 ],
844 )
845 })
846 })
847 })
848 }
849}
850
851#[derive(Debug)]
858pub enum ExecutionLevel {
859 AsInvoker,
861 HighestAvailable,
864 RequireAdministrator,
866}
867
868impl ExecutionLevel {
869 pub fn as_str(&self) -> &'static str {
870 match self {
871 Self::AsInvoker => "asInvoker",
872 Self::HighestAvailable => "highestAvailable",
873 Self::RequireAdministrator => "requireAdministrator",
874 }
875 }
876}
877
878impl Display for ExecutionLevel {
879 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
880 f.pad(self.as_str())
881 }
882}