快速面板模块

This commit is contained in:
zhongluofeng
2026-08-04 16:00:57 +08:00
parent 126f8896b6
commit c7578a2e6b
7 changed files with 954 additions and 117 deletions
+7 -4
View File
@@ -65,10 +65,11 @@ use clipboard::{
use quickpanel::{
quickpanel_build_file_index, quickpanel_clear_app_icon_cache, quickpanel_delete_file,
quickpanel_file_index_stats, quickpanel_get_app_icon, quickpanel_get_settings,
quickpanel_hide_popup, quickpanel_init_file_index, quickpanel_lock_screen,
quickpanel_open_file, quickpanel_register_shortcut, quickpanel_reveal_in_explorer,
quickpanel_run_custom_command, quickpanel_run_system_command, quickpanel_save_settings,
quickpanel_scan_apps, quickpanel_search_files, quickpanel_show_popup, quickpanel_show_window,
quickpanel_get_special_locations, quickpanel_hide_popup, quickpanel_init_file_index,
quickpanel_lock_screen, quickpanel_open_file, quickpanel_open_special,
quickpanel_register_shortcut, quickpanel_reveal_in_explorer, quickpanel_run_custom_command,
quickpanel_run_system_command, quickpanel_save_settings, quickpanel_scan_apps,
quickpanel_search_files, quickpanel_show_popup, quickpanel_show_window,
quickpanel_unregister_shortcut,
};
use tray_menu::{tray_menu_action, tray_menu_hide, tray_menu_ready};
@@ -225,6 +226,8 @@ pub fn run() {
quickpanel_delete_file,
quickpanel_run_custom_command,
quickpanel_run_system_command,
quickpanel_get_special_locations,
quickpanel_open_special,
tray_menu_action,
tray_menu_hide,
tray_menu_ready,
+21 -51
View File
@@ -188,62 +188,32 @@ pub fn quickpanel_reveal_in_explorer(path: String) -> Result<(), String> {
Ok(())
}
/// 用系统默认程序打开文件ShellExecuteW
/// 无关联应用时,自动 fallback 到「打开方式」对话框(verb: openas)。
/// 用系统默认程序打开文件/文件夹
/// - 目录:explorer.exe 直接打开(修复索引目录点击后未打开的问题)
/// - 文件:ShellExecuteW open,无关联应用时自动 fallback 到「打开方式」对话框(verb: openas
#[tauri::command]
pub fn quickpanel_open_file(path: String) -> Result<(), String> {
#[cfg(windows)]
{
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;
super::special_locations::open_path(&path)
}
let normalized = path.replace('/', "\\");
let wide_path: Vec<u16> = OsStr::new(&normalized)
.encode_wide()
.chain(std::iter::once(0))
.collect();
let wide_open: Vec<u16> = OsStr::new("open")
.encode_wide()
.chain(std::iter::once(0))
.collect();
let wide_openas: Vec<u16> = OsStr::new("openas")
.encode_wide()
.chain(std::iter::once(0))
.collect();
/// 获取 Windows 常用快捷位置(hosts、回收站、此电脑、用户目录、系统管理工具等)
#[tauri::command]
pub fn quickpanel_get_special_locations() -> Vec<super::special_locations::SpecialLocation> {
super::special_locations::get_special_locations()
}
unsafe {
let mut hinst = ShellExecuteW(
0 as HWND,
wide_open.as_ptr(),
wide_path.as_ptr(),
std::ptr::null(),
std::ptr::null(),
SW_SHOWNORMAL,
);
if hinst <= 32 {
hinst = ShellExecuteW(
0 as HWND,
wide_openas.as_ptr(),
wide_path.as_ptr(),
std::ptr::null(),
std::ptr::null(),
SW_SHOWNORMAL,
);
if hinst <= 32 {
return Err(format!("打开文件失败 (code: {})", hinst as i32));
}
}
}
/// 打开快捷位置(kind: file | shell | cmd
#[tauri::command]
pub fn quickpanel_open_special(
kind: String,
target: String,
args: Vec<String>,
) -> Result<(), String> {
match kind.as_str() {
"shell" => super::special_locations::open_shell(&target),
"cmd" => super::special_locations::open_cmd(&target, &args),
_ => super::special_locations::open_path(&target),
}
#[cfg(not(windows))]
{
let _ = path;
return Err(String::from("当前平台不支持"));
}
Ok(())
}
/// 删除文件(移到回收站)
+6 -4
View File
@@ -9,14 +9,16 @@ pub mod commands;
pub mod file_index;
pub mod icon_extractor;
pub mod popup;
pub mod special_locations;
pub use commands::{
quickpanel_build_file_index, quickpanel_clear_app_icon_cache, quickpanel_delete_file,
quickpanel_file_index_stats, quickpanel_get_app_icon, quickpanel_get_settings,
quickpanel_hide_popup, quickpanel_init_file_index, quickpanel_lock_screen,
quickpanel_open_file, quickpanel_register_shortcut, quickpanel_reveal_in_explorer,
quickpanel_run_custom_command, quickpanel_run_system_command, quickpanel_save_settings,
quickpanel_scan_apps, quickpanel_search_files, quickpanel_show_popup, quickpanel_show_window,
quickpanel_get_special_locations, quickpanel_hide_popup, quickpanel_init_file_index,
quickpanel_lock_screen, quickpanel_open_file, quickpanel_open_special,
quickpanel_register_shortcut, quickpanel_reveal_in_explorer, quickpanel_run_custom_command,
quickpanel_run_system_command, quickpanel_save_settings, quickpanel_scan_apps,
quickpanel_search_files, quickpanel_show_popup, quickpanel_show_window,
quickpanel_unregister_shortcut,
};
pub use popup::{ensure_window, load_settings, register_shortcut};
@@ -0,0 +1,232 @@
//! 快捷位置:Windows 常用系统位置/工具。
//!
//! 提供两类能力:
//! - `get_special_locations`:返回预定义列表(hosts、回收站、此电脑、用户目录、系统管理工具),
//! 路径在 Rust 侧解析(环境变量 / dirs crate),前端只负责展示与搜索。
//! - 打开逻辑:目录走 explorer.exe(比 ShellExecute "open" 目录更可靠),
//! 文件走 ShellExecuteWopen + openas fallback),shell 位置与系统工具走 ShellExecuteW。
use std::path::PathBuf;
use serde::Serialize;
/// 快捷位置条目
#[derive(Debug, Clone, Serialize)]
#[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")
}