1use std::fmt::{Debug, Formatter};
15
16pub use windows::Win32::UI::Shell::{
17 Common::ITEMIDLIST, FOLDERID_AccountPictures, FOLDERID_AddNewPrograms, FOLDERID_AdminTools,
18 FOLDERID_AllAppMods, FOLDERID_AppCaptures, FOLDERID_AppDataDesktop, FOLDERID_AppDataDocuments,
19 FOLDERID_AppDataFavorites, FOLDERID_AppDataProgramData, FOLDERID_AppUpdates,
20 FOLDERID_ApplicationShortcuts, FOLDERID_AppsFolder, FOLDERID_CDBurning, FOLDERID_CameraRoll,
21 FOLDERID_CameraRollLibrary, FOLDERID_ChangeRemovePrograms, FOLDERID_CommonAdminTools,
22 FOLDERID_CommonOEMLinks, FOLDERID_CommonPrograms, FOLDERID_CommonStartMenu,
23 FOLDERID_CommonStartMenuPlaces, FOLDERID_CommonStartup, FOLDERID_CommonTemplates,
24 FOLDERID_ComputerFolder, FOLDERID_ConflictFolder, FOLDERID_ConnectionsFolder,
25 FOLDERID_Contacts, FOLDERID_ControlPanelFolder, FOLDERID_Cookies, FOLDERID_CurrentAppMods,
26 FOLDERID_Desktop, FOLDERID_DevelopmentFiles, FOLDERID_Device, FOLDERID_DeviceMetadataStore,
27 FOLDERID_Documents, FOLDERID_DocumentsLibrary, FOLDERID_Downloads, FOLDERID_Favorites,
28 FOLDERID_Fonts, FOLDERID_GameTasks, FOLDERID_Games, FOLDERID_History, FOLDERID_HomeGroup,
29 FOLDERID_HomeGroupCurrentUser, FOLDERID_ImplicitAppShortcuts, FOLDERID_InternetCache,
30 FOLDERID_InternetFolder, FOLDERID_Libraries, FOLDERID_Links, FOLDERID_LocalAppData,
31 FOLDERID_LocalAppDataLow, FOLDERID_LocalDocuments, FOLDERID_LocalDownloads,
32 FOLDERID_LocalMusic, FOLDERID_LocalPictures, FOLDERID_LocalStorage, FOLDERID_LocalVideos,
33 FOLDERID_LocalizedResourcesDir, FOLDERID_Music, FOLDERID_MusicLibrary, FOLDERID_NetHood,
34 FOLDERID_NetworkFolder, FOLDERID_Objects3D, FOLDERID_OneDrive, FOLDERID_OriginalImages,
35 FOLDERID_PhotoAlbums, FOLDERID_Pictures, FOLDERID_PicturesLibrary, FOLDERID_Playlists,
36 FOLDERID_PrintHood, FOLDERID_PrintersFolder, FOLDERID_Profile, FOLDERID_ProgramData,
37 FOLDERID_ProgramFiles, FOLDERID_ProgramFilesCommon, FOLDERID_ProgramFilesCommonX64,
38 FOLDERID_ProgramFilesCommonX86, FOLDERID_ProgramFilesX64, FOLDERID_ProgramFilesX86,
39 FOLDERID_Programs, FOLDERID_Public, FOLDERID_PublicDesktop, FOLDERID_PublicDocuments,
40 FOLDERID_PublicDownloads, FOLDERID_PublicGameTasks, FOLDERID_PublicLibraries,
41 FOLDERID_PublicMusic, FOLDERID_PublicPictures, FOLDERID_PublicRingtones,
42 FOLDERID_PublicUserTiles, FOLDERID_PublicVideos, FOLDERID_QuickLaunch, FOLDERID_Recent,
43 FOLDERID_RecordedCalls, FOLDERID_RecordedTVLibrary, FOLDERID_RecycleBinFolder,
44 FOLDERID_ResourceDir, FOLDERID_RetailDemo, FOLDERID_Ringtones, FOLDERID_RoamedTileImages,
45 FOLDERID_RoamingAppData, FOLDERID_RoamingTiles, FOLDERID_SampleMusic, FOLDERID_SamplePictures,
46 FOLDERID_SamplePlaylists, FOLDERID_SampleVideos, FOLDERID_SavedGames, FOLDERID_SavedPictures,
47 FOLDERID_SavedPicturesLibrary, FOLDERID_SavedSearches, FOLDERID_Screenshots,
48 FOLDERID_SearchHistory, FOLDERID_SearchHome, FOLDERID_SearchTemplates, FOLDERID_SendTo,
49 FOLDERID_SidebarDefaultParts, FOLDERID_SidebarParts, FOLDERID_SkyDrive,
50 FOLDERID_SkyDriveCameraRoll, FOLDERID_SkyDriveDocuments, FOLDERID_SkyDriveMusic,
51 FOLDERID_SkyDrivePictures, FOLDERID_StartMenu, FOLDERID_StartMenuAllPrograms, FOLDERID_Startup,
52 FOLDERID_SyncManagerFolder, FOLDERID_SyncResultsFolder, FOLDERID_SyncSetupFolder,
53 FOLDERID_System, FOLDERID_SystemX86, FOLDERID_Templates, FOLDERID_UserPinned,
54 FOLDERID_UserProfiles, FOLDERID_UserProgramFiles, FOLDERID_UserProgramFilesCommon,
55 FOLDERID_UsersFiles, FOLDERID_UsersLibraries, FOLDERID_Videos, FOLDERID_VideosLibrary,
56 FOLDERID_Windows, FOLDERID_SEARCH_CSC, FOLDERID_SEARCH_MAPI, KF_FLAG_ALIAS_ONLY,
57 KF_FLAG_CREATE, KF_FLAG_DEFAULT, KF_FLAG_DEFAULT_PATH, KF_FLAG_DONT_UNEXPAND,
58 KF_FLAG_DONT_VERIFY, KF_FLAG_FORCE_APPCONTAINER_REDIRECTION,
59 KF_FLAG_FORCE_APP_DATA_REDIRECTION, KF_FLAG_FORCE_PACKAGE_REDIRECTION, KF_FLAG_INIT,
60 KF_FLAG_NOT_PARENT_RELATIVE, KF_FLAG_NO_ALIAS, KF_FLAG_NO_APPCONTAINER_REDIRECTION,
61 KF_FLAG_NO_PACKAGE_REDIRECTION, KF_FLAG_RETURN_FILTER_REDIRECTION_TARGET,
62 KF_FLAG_SIMPLE_IDLIST, KNOWN_FOLDER_FLAG, SLGP_FLAGS, SLGP_RAWPATH, SLGP_RELATIVEPRIORITY,
63 SLGP_SHORTPATH, SLGP_UNCPRIORITY, SLR_ANY_MATCH, SLR_FLAGS, SLR_INVOKE_MSI, SLR_KNOWNFOLDER,
64 SLR_MACHINE_IN_LOCAL_TARGET, SLR_NOLINKINFO, SLR_NONE, SLR_NOSEARCH, SLR_NOTRACK, SLR_NOUPDATE,
65 SLR_NO_OBJECT_ID, SLR_NO_UI, SLR_NO_UI_WITH_MSG_PUMP, SLR_OFFER_DELETE_WITHOUT_FILE,
66 SLR_UPDATE, SLR_UPDATE_MACHINE_AND_SID,
67};
68use windows::{
69 core::{Interface, GUID, HSTRING},
70 Win32::{
71 Foundation::MAX_PATH,
72 Storage::FileSystem::WIN32_FIND_DATAW,
73 System::Com::{CoCreateInstance, CLSCTX_INPROC_SERVER},
74 UI::Shell::{IShellLinkW, SHGetKnownFolderPath, ShellLink as SL},
75 },
76};
77
78use crate::{
79 com::persist::PersistFile,
80 common::{HANDLE, HWND, SHOW_WINDOW_CMD},
81 input::VIRTUAL_KEY,
82};
83
84pub fn get_known_folder_path(
93 rfid: &GUID,
94 flags: KNOWN_FOLDER_FLAG,
95 h_token: Option<HANDLE>,
96) -> Option<String> {
97 match unsafe { SHGetKnownFolderPath(rfid, flags, h_token) } {
98 Ok(p) => match unsafe { p.to_string() } {
99 Ok(s) => Some(s),
100 Err(_) => None,
101 },
102 Err(_) => None,
103 }
104}
105
106pub struct ShellLink(IShellLinkW);
108
109impl ShellLink {
111 pub fn new() -> Self {
115 let link = unsafe { CoCreateInstance::<_, IShellLinkW>(&SL, None, CLSCTX_INPROC_SERVER) }
116 .expect("Can't create the shell link.");
117 Self(link)
118 }
119
120 pub fn set_description(&self, description: String) -> &Self {
125 unsafe {
126 self.0
127 .SetDescription(&HSTRING::from(description))
128 .unwrap_or(())
129 }
130 self
131 }
132
133 pub fn get_description(&self) -> String {
135 let mut buf: [u16; 1024] = [0; 1024];
136 unsafe { self.0.GetDescription(&mut buf).unwrap_or(()) };
137 String::from_utf16_lossy(&buf)
138 .trim_matches('\0')
139 .to_string()
140 }
141
142 pub fn set_path(&self, path: String) -> &Self {
147 unsafe {
148 self.0.SetPath(&HSTRING::from(path)).unwrap_or(());
149 }
150 self
151 }
152
153 pub fn get_path(&self, flags: SLGP_FLAGS) -> (WIN32_FIND_DATAW, String) {
164 unsafe {
165 let mut buf: [u16; MAX_PATH as usize] = [0; MAX_PATH as usize];
166 let mut fd = std::mem::zeroed();
167 self.0
168 .GetPath(&mut buf, &mut fd, flags.0 as u32)
169 .unwrap_or(());
170 (
171 fd,
172 String::from_utf16_lossy(&buf)
173 .trim_matches('\0')
174 .to_string(),
175 )
176 }
177 }
178
179 pub fn set_arguments(&self, args: String) -> &Self {
186 unsafe {
187 self.0.SetArguments(&HSTRING::from(args)).unwrap_or(());
188 }
189 self
190 }
191
192 pub fn get_arguments(&self) -> String {
196 unsafe {
197 let mut buf: [u16; MAX_PATH as usize] = [0; MAX_PATH as usize];
198 self.0.GetArguments(&mut buf).unwrap_or(());
199 String::from_utf16_lossy(&buf)
200 .trim_matches('\0')
201 .to_string()
202 }
203 }
204
205 pub fn set_hotkey(&self, flags: u32, key: VIRTUAL_KEY) -> &ShellLink {
217 unsafe { self.0.SetHotkey((flags as u16) << 8 | key.0) }.unwrap_or(());
218 self
219 }
220
221 pub fn get_hotkey(&self) -> (u32, VIRTUAL_KEY) {
231 let hotkey = unsafe { self.0.GetHotkey() }.unwrap_or(0);
232 ((hotkey >> 8) as u32, VIRTUAL_KEY(hotkey))
233 }
234
235 pub fn set_icon_location(&self, path: String, index: i32) -> &ShellLink {
241 unsafe { self.0.SetIconLocation(&HSTRING::from(path), index) }.unwrap_or(());
242 self
243 }
244
245 pub fn get_icon_location(&self) -> (String, i32) {
249 unsafe {
250 let mut buf: [u16; MAX_PATH as usize] = [0; MAX_PATH as usize];
251 let mut icon = std::mem::zeroed();
252 self.0.GetIconLocation(&mut buf, &mut icon).unwrap_or(());
253 (
254 String::from_utf16_lossy(&buf)
255 .trim_matches('\0')
256 .to_string(),
257 icon,
258 )
259 }
260 }
261
262 pub fn set_id_list(&self, list: Option<ITEMIDLIST>) -> &ShellLink {
270 unsafe {
271 if let Some(list) = list {
272 self.0.SetIDList(&list)
273 } else {
274 self.0.SetIDList(std::ptr::null())
275 }
276 }
277 .unwrap_or(());
278 self
279 }
280
281 pub fn get_id_list(&self) -> Option<ITEMIDLIST> {
285 unsafe {
286 if let Ok(p) = self.0.GetIDList() {
287 return Some(*p);
288 }
289 }
290 None
291 }
292
293 pub fn set_relative_path(&self, path: String) -> &Self {
299 unsafe {
301 self.0
302 .SetRelativePath(&HSTRING::from(path), 0)
303 .unwrap_or(());
304 }
305 self
306 }
307
308 pub fn set_show_cmd(&self, show_cmd: SHOW_WINDOW_CMD) -> &ShellLink {
318 unsafe { self.0.SetShowCmd(show_cmd) }.unwrap_or(());
319 self
320 }
321
322 pub fn get_show_cmd(&self) -> SHOW_WINDOW_CMD {
327 unsafe { self.0.GetShowCmd().unwrap_or(SHOW_WINDOW_CMD::default()) }
328 }
329
330 pub fn set_working_directory(&self, dir: String) -> &Self {
336 unsafe {
337 self.0
338 .SetWorkingDirectory(&HSTRING::from(dir))
339 .unwrap_or(());
340 }
341 self
342 }
343
344 pub fn get_working_directory(&self) -> String {
348 unsafe {
349 let mut buf: [u16; MAX_PATH as usize] = [0; MAX_PATH as usize];
350 self.0.GetWorkingDirectory(&mut buf).unwrap_or(());
351 String::from_utf16_lossy(&buf)
352 .trim_matches('\0')
353 .to_string()
354 }
355 }
356
357 pub fn resolve(&self, h_wnd: HWND, flags: SLR_FLAGS) -> &Self {
386 unsafe { self.0.Resolve(h_wnd, flags.0 as u32) }.unwrap_or(());
387 self
388 }
389
390 pub fn open_file(&self) -> Option<PersistFile> {
394 if let Ok(f) = self.0.cast() {
395 return Some(PersistFile::from_raw(f));
396 }
397 None
398 }
399}
400
401impl Debug for ShellLink {
402 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
403 write!(f, "ShellLink()")
404 }
405}
406
407#[cfg(test)]
408mod test_shell {
409 use crate::{
410 com::co_initialize_multi_thread,
411 common::{HWND, SW_RESTORE},
412 input::VK_A,
413 shell::{
414 get_known_folder_path, FOLDERID_Desktop, FOLDERID_Profile, ShellLink, KF_FLAG_DEFAULT,
415 SLGP_SHORTPATH, SLR_NOSEARCH,
416 },
417 };
418
419 #[test]
421 fn main() {
422 co_initialize_multi_thread().ok().unwrap();
423 let link = ShellLink::new();
424 dbg!(link.set_description("rigela".to_string()).get_description());
425 dbg!(link
426 .set_path("D:\\rigela.exe".to_string())
427 .set_relative_path("D:\\rigela.lnk".to_string())
428 .get_path(SLGP_SHORTPATH));
429 dbg!(link.set_arguments("shell".to_string()).get_arguments());
430 dbg!(link.set_hotkey(0, VK_A).get_hotkey());
431 dbg!(link
432 .set_icon_location("logo.ico".to_string(), 0)
433 .get_icon_location());
434 dbg!(link.set_show_cmd(SW_RESTORE).get_show_cmd());
436 dbg!(link
437 .set_working_directory("D:\\".to_string())
438 .get_working_directory());
439 link.resolve(HWND::default(), SLR_NOSEARCH);
440 if let Some(file) = link.open_file() {
441 file.save(Some("D:\\rigela.lnk".to_string()), true);
442 }
443 dbg!(get_known_folder_path(
444 &FOLDERID_Desktop,
445 KF_FLAG_DEFAULT,
446 None,
447 ));
448 dbg!(get_known_folder_path(
449 &FOLDERID_Profile,
450 KF_FLAG_DEFAULT,
451 None,
452 ));
453
454 dbg!(link);
455 }
456}