235 lines
8.2 KiB
Rust
235 lines
8.2 KiB
Rust
//! 快捷位置:Windows 常用系统位置/工具。
|
||
//!
|
||
//! 提供两类能力:
|
||
//! - `get_special_locations`:返回预定义列表(hosts、回收站、此电脑、用户目录、系统管理工具),
|
||
//! 路径在 Rust 侧解析(环境变量 / dirs crate),前端只负责展示与搜索。
|
||
//! - 打开逻辑:目录走 explorer.exe(比 ShellExecute "open" 目录更可靠),
|
||
//! 文件走 ShellExecuteW(open + openas fallback),shell 位置与系统工具走 ShellExecuteW。
|
||
|
||
use std::path::PathBuf;
|
||
use serde::Serialize;
|
||
|
||
use specta::Type;
|
||
|
||
/// 快捷位置条目
|
||
#[derive(Debug, Clone, Serialize, Type)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct SpecialLocation {
|
||
pub id: String,
|
||
pub title: String,
|
||
pub subtitle: String,
|
||
pub keywords: Vec<String>,
|
||
/// file: 真实文件/文件夹路径;shell: explorer 打开的 shell 路径;cmd: 可执行命令
|
||
pub kind: String,
|
||
pub target: String,
|
||
pub args: Vec<String>,
|
||
}
|
||
|
||
fn sl(
|
||
id: &str,
|
||
title: &str,
|
||
subtitle: &str,
|
||
keywords: &[&str],
|
||
kind: &str,
|
||
target: &str,
|
||
args: &[&str],
|
||
) -> SpecialLocation {
|
||
SpecialLocation {
|
||
id: id.to_string(),
|
||
title: title.to_string(),
|
||
subtitle: subtitle.to_string(),
|
||
keywords: keywords.iter().map(|s| s.to_string()).collect(),
|
||
kind: kind.to_string(),
|
||
target: target.to_string(),
|
||
args: args.iter().map(|s| s.to_string()).collect(),
|
||
}
|
||
}
|
||
|
||
/// Windows 系统目录(%SystemRoot%)
|
||
fn system_root() -> String {
|
||
std::env::var("SystemRoot")
|
||
.or_else(|_| std::env::var("windir"))
|
||
.unwrap_or_else(|_| "C:\\Windows".to_string())
|
||
}
|
||
|
||
fn dir_str(d: Option<PathBuf>) -> Option<String> {
|
||
d.map(|p| p.to_string_lossy().to_string())
|
||
}
|
||
|
||
/// 返回全部快捷位置(路径已在 Rust 侧解析,避免前端硬编码)
|
||
pub fn get_special_locations() -> Vec<SpecialLocation> {
|
||
let mut list = Vec::new();
|
||
|
||
// ===== shell 位置(explorer 打开,CLSID 格式) =====
|
||
list.push(sl(
|
||
"sp-recycle-bin",
|
||
"回收站",
|
||
"Windows 回收站",
|
||
&["recycle", "bin", "trash", "回收站", "垃圾桶", "垃圾箱"],
|
||
"shell",
|
||
"::{645FF040-5081-101B-9F08-00AA002F954E}",
|
||
&[],
|
||
));
|
||
list.push(sl(
|
||
"sp-this-pc",
|
||
"此电脑",
|
||
"我的电脑",
|
||
&["此电脑", "我的电脑", "computer", "pc", "thispc"],
|
||
"shell",
|
||
"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}",
|
||
&[],
|
||
));
|
||
list.push(sl(
|
||
"sp-network",
|
||
"网络",
|
||
"网络位置",
|
||
&["网络", "network", "网上邻居"],
|
||
"shell",
|
||
"::{208D2C60-3AEA-1069-A2D7-08002B30309D}",
|
||
&[],
|
||
));
|
||
|
||
// ===== 常用文件 =====
|
||
let hosts = format!("{}\\System32\\drivers\\etc\\hosts", system_root());
|
||
list.push(sl(
|
||
"sp-hosts",
|
||
"hosts 文件",
|
||
&hosts,
|
||
&["hosts", "host", "主机文件", "域名映射"],
|
||
"file",
|
||
&hosts,
|
||
&[],
|
||
));
|
||
|
||
// ===== 用户目录(真实路径) =====
|
||
let user_dirs: &[(&str, &str, &[&str], Option<PathBuf>)] = &[
|
||
("sp-documents", "我的文档", &["文档", "我的文档", "documents", "docs"], dirs::document_dir()),
|
||
("sp-desktop", "桌面", &["桌面", "desktop"], dirs::desktop_dir()),
|
||
("sp-downloads", "下载", &["下载", "download", "downloads"], dirs::download_dir()),
|
||
("sp-pictures", "图片", &["图片", "照片", "pictures", "photos"], dirs::picture_dir()),
|
||
("sp-music", "音乐", &["音乐", "music"], dirs::audio_dir()),
|
||
("sp-videos", "视频", &["视频", "videos"], dirs::video_dir()),
|
||
];
|
||
for (id, title, keywords, path) in user_dirs {
|
||
if let Some(p) = dir_str(path.clone()) {
|
||
list.push(sl(id, title, &p, keywords, "file", &p, &[]));
|
||
}
|
||
}
|
||
|
||
// ===== 系统管理工具(ShellExecuteW 直接启动 .msc/.cpl/.exe) =====
|
||
let tools: &[(&str, &str, &[&str], &str)] = &[
|
||
("sp-devmgmt", "设备管理器", &["设备管理器", "设备", "devmgmt", "device"], "devmgmt.msc"),
|
||
("sp-diskmgmt", "磁盘管理", &["磁盘管理", "磁盘", "diskmgmt", "disk"], "diskmgmt.msc"),
|
||
("sp-compmgmt", "计算机管理", &["计算机管理", "compmgmt"], "compmgmt.msc"),
|
||
("sp-services", "服务", &["服务", "services", "service"], "services.msc"),
|
||
("sp-perfmon", "性能监视器", &["性能监视器", "性能", "perfmon"], "perfmon.msc"),
|
||
("sp-msinfo", "系统信息", &["系统信息", "msinfo", "systeminfo"], "msinfo32.exe"),
|
||
("sp-ncpa", "网络连接", &["网络连接", "ncpa", "网卡"], "ncpa.cpl"),
|
||
("sp-appwiz", "程序和功能", &["程序和功能", "卸载", "appwiz", "uninstall"], "appwiz.cpl"),
|
||
("sp-powercfg", "电源选项", &["电源选项", "电源", "powercfg", "power"], "powercfg.cpl"),
|
||
("sp-osk", "屏幕键盘", &["屏幕键盘", "键盘", "osk", "virtual keyboard"], "osk.exe"),
|
||
("sp-magnify", "放大镜", &["放大镜", "magnify", "magnifier"], "magnify.exe"),
|
||
];
|
||
for (id, title, keywords, exe) in tools {
|
||
list.push(sl(id, title, exe, keywords, "cmd", exe, &[]));
|
||
}
|
||
list.push(sl(
|
||
"sp-envvar",
|
||
"环境变量",
|
||
"rundll32 sysdm.cpl,EditEnvironmentVariables",
|
||
&["环境变量", "环境", "env", "environment"],
|
||
"cmd",
|
||
"rundll32.exe",
|
||
&["sysdm.cpl,EditEnvironmentVariables"],
|
||
));
|
||
|
||
list
|
||
}
|
||
|
||
// ===== 打开逻辑 =====
|
||
|
||
/// ShellExecuteW 调用(verb 可指定,如 "open" / "openas")
|
||
#[cfg(windows)]
|
||
fn shell_execute_verb(exe: &str, params: &str, verb: &str) -> Result<(), String> {
|
||
use std::ffi::OsStr;
|
||
use std::os::windows::ffi::OsStrExt;
|
||
use windows_sys::Win32::Foundation::HWND;
|
||
use windows_sys::Win32::UI::Shell::ShellExecuteW;
|
||
use windows_sys::Win32::UI::WindowsAndMessaging::SW_SHOWNORMAL;
|
||
|
||
let wide_exe: Vec<u16> = OsStr::new(exe)
|
||
.encode_wide()
|
||
.chain(std::iter::once(0))
|
||
.collect();
|
||
let wide_params: Vec<u16> = OsStr::new(params)
|
||
.encode_wide()
|
||
.chain(std::iter::once(0))
|
||
.collect();
|
||
let wide_verb: Vec<u16> = OsStr::new(verb)
|
||
.encode_wide()
|
||
.chain(std::iter::once(0))
|
||
.collect();
|
||
|
||
unsafe {
|
||
let hinst = ShellExecuteW(
|
||
0 as HWND,
|
||
wide_verb.as_ptr(),
|
||
wide_exe.as_ptr(),
|
||
wide_params.as_ptr(),
|
||
std::ptr::null(),
|
||
SW_SHOWNORMAL,
|
||
);
|
||
// ShellExecuteW 返回值 <= 32 表示错误
|
||
if hinst <= 32 {
|
||
return Err(format!("打开失败 (code: {})", hinst as i32));
|
||
}
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
#[cfg(not(windows))]
|
||
fn shell_execute_verb(_exe: &str, _params: &str, _verb: &str) -> Result<(), String> {
|
||
Err("当前平台不支持".into())
|
||
}
|
||
|
||
/// 打开文件/文件夹路径:
|
||
/// - 目录:explorer.exe 直接打开(比 ShellExecute "open" 目录更可靠,修复索引目录点击不生效问题)
|
||
/// - 文件:ShellExecuteW open,无关联程序时 fallback 到「打开方式」对话框
|
||
pub fn open_path(path: &str) -> Result<(), String> {
|
||
let p = std::path::Path::new(path);
|
||
#[cfg(windows)]
|
||
{
|
||
if p.is_dir() {
|
||
use crate::process_manager::setup_creation_flags;
|
||
let mut cmd = std::process::Command::new("explorer.exe");
|
||
cmd.arg(path);
|
||
setup_creation_flags(&mut cmd);
|
||
return cmd
|
||
.spawn()
|
||
.map(|_| ())
|
||
.map_err(|e| format!("打开文件夹失败: {}", e));
|
||
}
|
||
let params = String::new();
|
||
if shell_execute_verb(path, ¶ms, "open").is_err() {
|
||
return shell_execute_verb(path, ¶ms, "openas");
|
||
}
|
||
Ok(())
|
||
}
|
||
#[cfg(not(windows))]
|
||
{
|
||
let _ = p;
|
||
Err("当前平台不支持".into())
|
||
}
|
||
}
|
||
|
||
/// 打开 shell 位置(explorer shell: 或 ::{CLSID})
|
||
pub fn open_shell(target: &str) -> Result<(), String> {
|
||
shell_execute_verb("explorer.exe", target, "open")
|
||
}
|
||
|
||
/// 打开系统工具(.msc / .cpl / .exe,参数空格连接)
|
||
pub fn open_cmd(exe: &str, args: &[String]) -> Result<(), String> {
|
||
let params = args.join(" ");
|
||
shell_execute_verb(exe, ¶ms, "open")
|
||
}
|