1const WINMD_NAME: &str = "Microsoft.CommandPalette.Extensions.winmd";
6const WINMD_DATA: &[u8] = include_bytes!("Microsoft.CommandPalette.Extensions.winmd");
7
8fn get_cargo_artifact_dir() -> Result<std::path::PathBuf, Box<dyn std::error::Error>> {
14 let skip_triple = std::env::var("TARGET")? == std::env::var("HOST")?;
15 let skip_parent_dirs = if skip_triple { 3 } else { 4 };
16
17 let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR")?);
18 let mut current = out_dir.as_path();
19 for _ in 0..skip_parent_dirs {
20 current = current.parent().ok_or("not found")?;
21 }
22
23 Ok(std::path::PathBuf::from(current))
24}
25
26pub fn generate_winmd() -> Result<(), Box<dyn std::error::Error>> {
29 let artifact_dir = get_cargo_artifact_dir()?;
30 let winmd_path = std::path::Path::new(&artifact_dir).join(WINMD_NAME);
31 std::fs::write(&winmd_path, WINMD_DATA)?;
32 Ok(())
33}
34
35pub struct AppxManifest {
39 id: String,
40 publisher_id: String,
41 version: String,
42 logo: String,
43 display_name: String,
44 publisher_display_name: String,
45 description: String,
46 executable: String,
47 arguments: String,
48 classes: Vec<(String, String)>, }
50
51impl AppxManifest {
52 pub fn generate_xml(&self) -> String {
54 let com_classes: Vec<String> = self
55 .classes
56 .iter()
57 .map(|(class_id, display)| {
58 format!(
59 r#"<com:Class Id="{}" DisplayName="{}" />"#,
60 class_id, display
61 )
62 })
63 .collect();
64 let activation_classes: Vec<String> = self
65 .classes
66 .iter()
67 .map(|(class_id, _)| format!(r#"<CreateInstance ClassId="{}" />"#, class_id))
68 .collect();
69
70 format!(
71 r#"<?xml version="1.0" encoding="utf-8"?>
72
73<Package
74 xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
75 xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
76 xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
77 xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
78 xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
79 IgnorableNamespaces="uap uap3 rescap">
80
81 <Identity
82 Name="{id}"
83 Publisher="{publisher_id}"
84 Version="{version}" />
85
86 <Properties>
87 <DisplayName>{display_name}</DisplayName>
88 <PublisherDisplayName>{publisher_display_name}</PublisherDisplayName>
89 <Logo>{logo}</Logo>
90 </Properties>
91
92 <Dependencies>
93 <TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
94 <TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
95 </Dependencies>
96
97 <Applications>
98 <Application Id="App"
99 Executable="{executable}"
100 EntryPoint="Windows.FullTrustApplication">
101 <uap:VisualElements
102 DisplayName="{display_name}"
103 Description="{description}"
104 BackgroundColor="transparent"
105 Square150x150Logo="Assets\Square150x150Logo.png"
106 Square44x44Logo="Assets\Square44x44Logo.png">
107 </uap:VisualElements>
108 <Extensions>
109 <com:Extension Category="windows.comServer">
110 <com:ComServer>
111 <com:ExeServer Executable="{executable}" Arguments="{arguments}" DisplayName="{display_name}">
112 {com_classes}
113 </com:ExeServer>
114 </com:ComServer>
115 </com:Extension>
116 <uap3:Extension Category="windows.appExtension">
117 <uap3:AppExtension Name="com.microsoft.commandpalette"
118 Id="ID"
119 PublicFolder="Public"
120 DisplayName="{display_name}"
121 Description="{description}">
122 <uap3:Properties>
123 <CmdPalProvider>
124 <Activation>
125 {activation_classes}
126 </Activation>
127 <SupportedInterfaces>
128 <Commands/>
129 </SupportedInterfaces>
130 </CmdPalProvider>
131 </uap3:Properties>
132 </uap3:AppExtension>
133 </uap3:Extension>
134 </Extensions>
135 </Application>
136 </Applications>
137
138 <Capabilities>
139 <Capability Name="internetClient" />
140 <rescap:Capability Name="runFullTrust" />
141 </Capabilities>
142</Package>
143"#,
144 id = self.id,
145 publisher_id = self.publisher_id,
146 display_name = self.display_name,
147 publisher_display_name = self.publisher_display_name,
148 version = self.version,
149 description = self.description,
150 logo = self.logo,
151 executable = self.executable,
152 arguments = self.arguments,
153 com_classes = com_classes.join("\n"),
154 activation_classes = activation_classes.join("\n"),
155 )
156 }
157
158 pub fn write_xml(&self) -> Result<(), Box<dyn std::error::Error>> {
160 let artifact_dir = get_cargo_artifact_dir()?;
161 let manifest_path = artifact_dir.join("AppxManifest.xml");
162 std::fs::write(&manifest_path, self.generate_xml())?;
163 Ok(())
164 }
165}
166
167#[derive(Default)]
169pub struct AppxManifestBuilder {
170 id: Option<String>,
171 publisher_id: Option<String>,
172 version: Option<String>,
173 logo: Option<String>,
174 display_name: Option<String>,
175 publisher_display_name: Option<String>,
176 description: Option<String>,
177 executable: Option<String>,
178 arguments: Option<String>,
179 classes: Vec<(String, Option<String>)>, }
181
182impl AppxManifestBuilder {
183 pub fn new() -> Self {
185 Self::default()
186 }
187
188 pub fn id(mut self, id: impl Into<String>) -> Self {
192 self.id = Some(id.into());
193 self
194 }
195
196 pub fn publisher_id(mut self, publisher_id: impl Into<String>) -> Self {
200 self.publisher_id = Some(publisher_id.into());
201 self
202 }
203
204 pub fn version(mut self, version: impl Into<String>) -> Self {
211 self.version = Some(version.into());
212 self
213 }
214
215 pub fn logo(mut self, logo: impl Into<String>) -> Self {
218 self.logo = Some(logo.into());
219 self
220 }
221
222 pub fn display_name(mut self, display_name: impl Into<String>) -> Self {
225 self.display_name = Some(display_name.into());
226 self
227 }
228
229 pub fn publisher_display_name(mut self, publisher_display_name: impl Into<String>) -> Self {
232 self.publisher_display_name = Some(publisher_display_name.into());
233 self
234 }
235
236 pub fn description(mut self, description: impl Into<String>) -> Self {
238 self.description = Some(description.into());
239 self
240 }
241
242 pub fn executable(mut self, executable: impl Into<String>) -> Self {
246 self.executable = Some(executable.into());
247 self
248 }
249
250 pub fn arguments(mut self, arguments: impl Into<String>) -> Self {
254 self.arguments = Some(arguments.into());
255 self
256 }
257
258 pub fn class(mut self, class_id: impl Into<String>, display_name: Option<&str>) -> Self {
262 let display_name = display_name.map(|d| d.into());
263 self.classes.push((class_id.into(), display_name));
264 self
265 }
266
267 pub fn class_u128(self, class_id: u128, display_name: Option<&str>) -> Self {
271 let class_id = format!(
272 "{:08x}-{:04x}-{:04x}-{:04x}-{:012x}",
273 (class_id >> 96) as u32,
274 (class_id >> 80) as u16,
275 (class_id >> 64) as u16,
276 (class_id >> 48) as u16,
277 class_id & 0xFFFFFFFFFFFF
278 );
279 self.class(class_id, display_name)
280 }
281
282 fn infer_executable() -> String {
283 let inferred_name = std::env::var("CARGO_PKG_NAME")
284 .ok()
285 .or_else(|| std::env::var("CARGO_BIN_NAME").ok())
286 .unwrap_or("cmdpal-extension".into());
287 println!("cargo::warning=executable is not set, inferred '{}' as default", inferred_name);
288 format!("{}.exe", inferred_name)
289 }
290
291 fn infer_version() -> String {
292 let version = std::env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| {
293 println!("cargo::warning=CARGO_PKG_VERSION is not set, using '0.1.0' as base");
294 "0.1.0".into()
295 });
296
297 version
298 .split_once('-')
299 .map_or_else(|| version.as_str(), |(v, _)| v)
300 .split('.')
301 .map(|s| s.to_string()) .collect::<Vec<String>>()
303 .join(".")
304 + ".0" }
306
307 pub fn build(self) -> AppxManifest {
309 let id = self.id.expect("id is required");
310 let publisher_id = self.publisher_id.unwrap_or_else(|| {
311 println!("cargo::warning=publisher_id is not set, using default 'CN=Unknown'");
312 "CN=Unknown".into()
313 });
314 let version = self.version.unwrap_or_else(Self::infer_version);
315 let logo = self.logo.unwrap_or("Assets\\StoreLogo.png".into());
316 let display_name = self.display_name.unwrap_or_else(|| id.clone());
317 let publisher_display_name = self.publisher_display_name.unwrap_or("Unknown".into());
318 let description = self.description.unwrap_or_else(|| display_name.clone());
319 let executable = self.executable.unwrap_or_else(Self::infer_executable);
320 let arguments = self.arguments.unwrap_or("-RegisterAsComServer".into());
321 let classes: Vec<(String, String)> = self
322 .classes
323 .into_iter()
324 .map(|(class_id, display)| {
325 let display = display.unwrap_or_else(|| display_name.clone());
326 (class_id, display)
327 })
328 .collect();
329
330 AppxManifest {
331 id,
332 publisher_id,
333 version,
334 logo,
335 display_name,
336 publisher_display_name,
337 description,
338 executable,
339 arguments,
340 classes,
341 }
342 }
343}