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="PG-SP-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 <rescap:Capability Name="runFullTrust" />
140 </Capabilities>
141</Package>
142"#,
143 id = self.id,
144 publisher_id = self.publisher_id,
145 display_name = self.display_name,
146 publisher_display_name = self.publisher_display_name,
147 version = self.version,
148 description = self.description,
149 logo = self.logo,
150 executable = self.executable,
151 arguments = self.arguments,
152 com_classes = com_classes.join("\n"),
153 activation_classes = activation_classes.join("\n"),
154 )
155 }
156
157 pub fn write_xml(&self) -> Result<(), Box<dyn std::error::Error>> {
159 let artifact_dir = get_cargo_artifact_dir()?;
160 let manifest_path = artifact_dir.join("AppxManifest.xml");
161 std::fs::write(&manifest_path, self.generate_xml())?;
162 Ok(())
163 }
164}
165
166#[derive(Default)]
168pub struct AppxManifestBuilder {
169 id: Option<String>,
170 publisher_id: Option<String>,
171 version: Option<String>,
172 logo: Option<String>,
173 display_name: Option<String>,
174 publisher_display_name: Option<String>,
175 description: Option<String>,
176 executable: Option<String>,
177 arguments: Option<String>,
178 classes: Vec<(String, Option<String>)>, }
180
181impl AppxManifestBuilder {
182 pub fn new() -> Self {
184 Self::default()
185 }
186
187 pub fn id(mut self, id: impl Into<String>) -> Self {
191 self.id = Some(id.into());
192 self
193 }
194
195 pub fn publisher_id(mut self, publisher_id: impl Into<String>) -> Self {
199 self.publisher_id = Some(publisher_id.into());
200 self
201 }
202
203 pub fn version(mut self, version: impl Into<String>) -> Self {
210 self.version = Some(version.into());
211 self
212 }
213
214 pub fn logo(mut self, logo: impl Into<String>) -> Self {
217 self.logo = Some(logo.into());
218 self
219 }
220
221 pub fn display_name(mut self, display_name: impl Into<String>) -> Self {
224 self.display_name = Some(display_name.into());
225 self
226 }
227
228 pub fn publisher_display_name(mut self, publisher_display_name: impl Into<String>) -> Self {
231 self.publisher_display_name = Some(publisher_display_name.into());
232 self
233 }
234
235 pub fn description(mut self, description: impl Into<String>) -> Self {
237 self.description = Some(description.into());
238 self
239 }
240
241 pub fn executable(mut self, executable: impl Into<String>) -> Self {
245 self.executable = Some(executable.into());
246 self
247 }
248
249 pub fn arguments(mut self, arguments: impl Into<String>) -> Self {
253 self.arguments = Some(arguments.into());
254 self
255 }
256
257 pub fn class(mut self, class_id: impl Into<String>, display_name: Option<&str>) -> Self {
261 let display_name = display_name.map(|d| d.into());
262 self.classes.push((class_id.into(), display_name));
263 self
264 }
265
266 pub fn class_u128(self, class_id: u128, display_name: Option<&str>) -> Self {
270 let class_id = format!(
271 "{:08x}-{:04x}-{:04x}-{:04x}-{:012x}",
272 (class_id >> 96) as u32,
273 (class_id >> 80) as u16,
274 (class_id >> 64) as u16,
275 (class_id >> 48) as u16,
276 class_id & 0xFFFFFFFFFFFF
277 );
278 self.class(class_id, display_name)
279 }
280
281 fn infer_executable() -> String {
282 std::env::var("CARGO_BIN_NAME").unwrap_or_else(|_| {
283 println!("cargo::warning=CARGO_BIN_NAME is not set, using 'cmdpal-extension'");
284 "cmdpal-extension".into()
285 }) + ".exe"
286 }
287
288 fn infer_version() -> String {
289 let version = std::env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| {
290 println!("cargo::warning=CARGO_PKG_VERSION is not set, using '0.1.0' as base");
291 "0.1.0".into()
292 });
293
294 version
295 .split_once('-')
296 .map_or_else(|| version.as_str(), |(v, _)| v)
297 .split('.')
298 .map(|s| s.to_string()) .collect::<Vec<String>>()
300 .join(".")
301 + ".0" }
303
304 pub fn build(self) -> AppxManifest {
306 let id = self.id.expect("id is required");
307 let publisher_id = self.publisher_id.unwrap_or_else(|| {
308 println!("cargo::warning=publisher_id is not set, using default 'CN=Unknown'");
309 "CN=Unknown".into()
310 });
311 let version = self.version.unwrap_or_else(Self::infer_version);
312 let logo = self.logo.unwrap_or("Assets\\StoreLogo.png".into());
313 let display_name = self.display_name.unwrap_or_else(|| id.clone());
314 let publisher_display_name = self.publisher_display_name.unwrap_or("Unknown".into());
315 let description = self.description.unwrap_or_else(|| display_name.clone());
316 let executable = self.executable.unwrap_or_else(Self::infer_executable);
317 let arguments = self.arguments.unwrap_or("-RegisterAsComServer".into());
318 let classes: Vec<(String, String)> = self
319 .classes
320 .into_iter()
321 .map(|(class_id, display)| {
322 let display = display.unwrap_or_else(|| display_name.clone());
323 (class_id, display)
324 })
325 .collect();
326
327 AppxManifest {
328 id,
329 publisher_id,
330 version,
331 logo,
332 display_name,
333 publisher_display_name,
334 description,
335 executable,
336 arguments,
337 classes,
338 }
339 }
340}