快速面板模块

This commit is contained in:
zhongluofeng
2026-08-04 13:37:42 +08:00
parent 5656193b5c
commit 126f8896b6
23 changed files with 4048 additions and 52 deletions
+320
View File
@@ -0,0 +1,320 @@
//! Tauri 命令:快速面板模块
use tauri::AppHandle;
use super::popup::{self, QuickPanelSettings};
use super::{file_index, app_scanner, icon_extractor};
/// 读取快速面板设置(快捷键等)
#[tauri::command]
pub async fn quickpanel_get_settings(app: AppHandle) -> Result<QuickPanelSettings, String> {
Ok(popup::load_settings(&app))
}
/// 保存快速面板设置;快捷键变化时自动重新注册 + 预创建窗口
#[tauri::command]
pub async fn quickpanel_save_settings(
settings: QuickPanelSettings,
app: AppHandle,
) -> Result<(), String> {
let prev_shortcut = popup::load_settings(&app).shortcut;
popup::save_settings(&app, &settings)?;
// 快捷键变化时重新注册
if settings.shortcut != prev_shortcut {
popup::register_shortcut(&app, &settings.shortcut)?;
// 新快捷键非空时确保弹窗窗口已预创建
if !settings.shortcut.trim().is_empty() {
popup::ensure_window(&app);
}
}
Ok(())
}
/// 注册(或切换)快速面板全局快捷键
#[tauri::command]
pub async fn quickpanel_register_shortcut(
shortcut: String,
app: AppHandle,
) -> Result<(), String> {
popup::register_shortcut(&app, &shortcut)
}
/// 注销快速面板全局快捷键
#[tauri::command]
pub async fn quickpanel_unregister_shortcut(app: AppHandle) -> Result<(), String> {
popup::unregister_shortcut(&app);
Ok(())
}
/// 手动触发显示快速面板(供 UI 按钮调用)
#[tauri::command]
pub async fn quickpanel_show_popup(app: AppHandle) -> Result<(), String> {
popup::show_popup(&app);
Ok(())
}
/// 隐藏快速面板
#[tauri::command]
pub async fn quickpanel_hide_popup(app: AppHandle) -> Result<(), String> {
popup::hide_popup(&app);
Ok(())
}
/// 显示已创建的弹窗窗口(前端 onMounted 后调用)
#[tauri::command]
pub async fn quickpanel_show_window(app: AppHandle) -> Result<(), String> {
popup::show_window(&app);
Ok(())
}
/// 锁定屏幕(Windows: rundll32 user32.dll,LockWorkStationCREATE_NO_WINDOW 避免黑窗)
#[tauri::command]
pub fn quickpanel_lock_screen() -> Result<(), String> {
#[cfg(windows)]
{
use crate::process_manager::setup_creation_flags;
let mut cmd = std::process::Command::new("rundll32.exe");
cmd.arg("user32.dll,LockWorkStation");
setup_creation_flags(&mut cmd);
cmd.spawn().map_err(|e| format!("锁屏失败: {}", e))?;
}
#[cfg(not(windows))]
{
return Err("当前平台不支持锁屏".into());
}
Ok(())
}
/// 初始化文件索引数据库(应用启动时调用)
#[tauri::command]
pub fn quickpanel_init_file_index(app: AppHandle) -> Result<(), String> {
file_index::init(&app);
Ok(())
}
/// 构建文件索引(全量重建,阻塞操作建议在 spawn_blocking 调用)
#[tauri::command]
pub async fn quickpanel_build_file_index(app: AppHandle) -> Result<i64, String> {
let settings = popup::load_settings(&app);
let dirs = if settings.index_dirs.is_empty() {
popup::QuickPanelSettings::default().index_dirs
} else {
settings.index_dirs
};
// 阻塞操作放到 spawn_blocking
tauri::async_runtime::spawn_blocking(move || file_index::build_index(&dirs))
.await
.map_err(|e| format!("索引任务失败: {}", e))?
}
/// 搜索文件索引
#[tauri::command]
pub fn quickpanel_search_files(query: String, limit: Option<i64>) -> Vec<file_index::FileRecord> {
file_index::search(&query, limit.unwrap_or(50))
}
/// 获取索引状态
#[tauri::command]
pub fn quickpanel_file_index_stats() -> file_index::IndexStats {
file_index::stats()
}
/// 扫描已安装应用
#[tauri::command]
pub fn quickpanel_scan_apps() -> Vec<app_scanner::AppRecord> {
app_scanner::scan_apps()
}
/// 获取应用图标(data URL)。命中内存/磁盘缓存时零 Windows API 调用。
/// 前端按需为可见项调用,避免一次性加载全部图标。
#[tauri::command]
pub fn quickpanel_get_app_icon(app: AppHandle, path: String) -> Option<String> {
icon_extractor::get_icon_data_url(&app, &path)
}
/// 清理图标缓存(磁盘 + 内存)
#[tauri::command]
pub fn quickpanel_clear_app_icon_cache(app: AppHandle) -> Result<(), String> {
icon_extractor::clear_cache(&app);
Ok(())
}
/// 在资源管理器中显示文件(选中)
#[tauri::command]
pub fn quickpanel_reveal_in_explorer(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;
// 规范化路径分隔符
let normalized = path.replace('/', "\\");
// explorer.exe /select,"path" — 用 ShellExecuteW 直接传参,
// 避免 std::process::Command 的 arg 转义破坏 /select 语法。
// 对 .lnk 文件也能正确选中(explorer 直接选中 .lnk 文件本身)。
let params = format!("/select,\"{}\"", normalized);
let wide_exe: Vec<u16> = OsStr::new("explorer.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();
unsafe {
let hinst = ShellExecuteW(
0 as HWND,
std::ptr::null(),
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));
}
}
}
#[cfg(not(windows))]
{
let _ = path;
return Err("当前平台不支持".into());
}
Ok(())
}
/// 用系统默认程序打开文件(ShellExecuteW)。
/// 无关联应用时,自动 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;
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();
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));
}
}
}
}
#[cfg(not(windows))]
{
let _ = path;
return Err(String::from("当前平台不支持"));
}
Ok(())
}
/// 删除文件(移到回收站)
#[tauri::command]
pub fn quickpanel_delete_file(path: String) -> Result<(), String> {
#[cfg(windows)]
{
use crate::process_manager::setup_creation_flags;
let p = std::path::Path::new(&path);
let is_dir = p.is_dir();
// 用 PowerShell + Microsoft.VisualBasic 移到回收站
let script = if is_dir {
format!(
"Add-Type -AssemblyName Microsoft.VisualBasic; [Microsoft.VisualBasic.FileIO.FileSystem]::DeleteDirectory('{}','OnlyErrorDialogs','SendToRecycleBin')",
path.replace('\'', "''")
)
} else {
format!(
"Add-Type -AssemblyName Microsoft.VisualBasic; [Microsoft.VisualBasic.FileIO.FileSystem]::DeleteFile('{}','OnlyErrorDialogs','SendToRecycleBin')",
path.replace('\'', "''")
)
};
let mut cmd = std::process::Command::new("powershell.exe");
cmd.args(["-NoProfile", "-NonInteractive", "-Command", &script]);
setup_creation_flags(&mut cmd);
let output = cmd.output().map_err(|e| format!("删除失败: {}", e))?;
if !output.status.success() {
return Err(format!(
"删除失败: {}",
String::from_utf8_lossy(&output.stderr)
));
}
}
#[cfg(not(windows))]
{
let _ = path;
return Err("当前平台不支持".into());
}
Ok(())
}
/// 运行自定义命令(执行可执行文件 + 参数)
/// .lnk 快捷方式不能直接 spawnos error 193),需通过 cmd /C 启动
#[tauri::command]
pub fn quickpanel_run_custom_command(command: String, args: Vec<String>) -> Result<(), String> {
use crate::process_manager::setup_creation_flags;
let is_lnk = command
.to_lowercase()
.ends_with(".lnk");
let mut cmd = if is_lnk {
// cmd /C start "" "path.lnk" arg1 arg2
let mut c = std::process::Command::new("cmd");
c.args(["/C", "start", "", &command]);
c.args(&args);
c
} else {
let mut c = std::process::Command::new(&command);
c.args(&args);
c
};
setup_creation_flags(&mut cmd);
cmd.spawn().map_err(|e| format!("运行命令失败: {}", e))?;
Ok(())
}
/// 运行系统命令(不设置 CREATE_NO_WINDOW,使 cmd/powershell/regedit 等显示自身窗口)
/// 适用于内置系统工具:regedit、shutdown、cmd、powershell、taskmgr 等。
#[tauri::command]
pub fn quickpanel_run_system_command(command: String, args: Vec<String>) -> Result<(), String> {
let mut cmd = std::process::Command::new(&command);
cmd.args(&args);
cmd.spawn().map_err(|e| format!("运行系统命令失败: {}", e))?;
Ok(())
}