Files
Thing/src-tauri/src/quickpanel/special_locations.rs
T
2026-08-06 10:33:16 +08:00

235 lines
8.2 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 快捷位置:Windows 常用系统位置/工具。
//!
//! 提供两类能力:
//! - `get_special_locations`:返回预定义列表(hosts、回收站、此电脑、用户目录、系统管理工具),
//! 路径在 Rust 侧解析(环境变量 / dirs crate),前端只负责展示与搜索。
//! - 打开逻辑:目录走 explorer.exe(比 ShellExecute "open" 目录更可靠),
//! 文件走 ShellExecuteWopen + 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, &params, "open").is_err() {
return shell_execute_verb(path, &params, "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, &params, "open")
}