//! 应用扫描:Windows 开始菜单 .lnk + PATH 中的可执行文件。 //! //! 简化实现:扫描开始菜单目录(系统 + 用户)下的 .lnk 快捷方式, //! 名称取文件名(去 .lnk 后缀)。PATH 可执行文件扫描可选(避免噪音过多)。 //! 结果不持久化,每次唤起时按需刷新(数据量小,几十毫秒内完成)。 use std::path::PathBuf; use serde::Serialize; use walkdir::WalkDir; use specta::Type; #[derive(Debug, Clone, Serialize, Type)] #[serde(rename_all = "camelCase")] pub struct AppRecord { pub name: String, pub path: String, } /// 扫描开始菜单(系统 + 用户)。返回去重后的应用列表。 pub fn scan_apps() -> Vec { let mut apps = Vec::new(); let mut seen = std::collections::HashSet::new(); // 开始菜单目录 let mut dirs = Vec::new(); // 系统开始菜单:C:\ProgramData\Microsoft\Windows\Start Menu\Programs if let Ok(prog_data) = std::env::var("ProgramData") { dirs.push( PathBuf::from(prog_data) .join("Microsoft") .join("Windows") .join("Start Menu") .join("Programs"), ); } // 用户开始菜单:%APPDATA%\Microsoft\Windows\Start Menu\Programs if let Ok(appdata) = std::env::var("APPDATA") { dirs.push( PathBuf::from(appdata) .join("Microsoft") .join("Windows") .join("Start Menu") .join("Programs"), ); } for dir in dirs { if !dir.exists() { continue; } for entry in WalkDir::new(&dir) .max_depth(5) .follow_links(false) .into_iter() .filter_map(|e| e.ok()) { let p = entry.path(); if !p.is_file() { continue; } let ext = p.extension().map(|e| e.to_string_lossy().to_lowercase()).unwrap_or_default(); if ext != "lnk" { continue; } let Some(name_os) = p.file_stem() else { continue }; let name = name_os.to_string_lossy().to_string(); let path_str = p.to_string_lossy().to_string(); // 去重:同名应用保留第一个 if seen.insert(name.to_lowercase()) { apps.push(AppRecord { name, path: path_str }); } } } // 按名称排序 apps.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase())); apps }