优化,贴图

This commit is contained in:
zhongluofeng
2026-08-07 18:09:13 +08:00
parent e09b0567d6
commit d09599fa95
40 changed files with 1627 additions and 493 deletions
+3
View File
@@ -10,6 +10,8 @@ pub mod windows {
pub const OSD_OVERLAY: &str = "osd-overlay";
#[allow(dead_code)]
pub const SCREENSHOT_OVERLAY: &str = "screenshot-overlay";
#[allow(dead_code)]
pub const SCREENSHOT_PIN: &str = "screenshot-pin";
}
/// Tauri 事件名(与前端 constants::EVENTS 对应)
@@ -41,6 +43,7 @@ pub mod events {
pub const OSD_END_DRAG: &str = "osd-end-drag";
// 截图
pub const SCREENSHOT_SHORTCUT: &str = "screenshot-shortcut";
pub const SCREENSHOT_PIN_SHORTCUT: &str = "screenshot-pin-shortcut";
// 内核安装进度
pub const KERNEL_INSTALL_PROGRESS: &str = "kernel-install-progress";
// 进程与下载
+18 -12
View File
@@ -34,10 +34,10 @@ use mihomo_manager::{
proxy_test_delay, proxy_update_kernel, proxy_update_profile, proxy_version, MihomoManager,
};
use monitor_kernel::{
monitor_elevate_self, monitor_get_elevate_on_launch, monitor_get_hardware_config,
monitor_get_snapshot, monitor_get_status, monitor_kernel_info, monitor_set_elevate_on_launch,
monitor_set_hardware_config, monitor_start, monitor_start_elevated, monitor_status,
monitor_stop, MonitorKernel,
monitor_elevate_self, monitor_get_auto_start, monitor_get_elevate_on_launch,
monitor_get_hardware_config, monitor_get_snapshot, monitor_get_status, monitor_kernel_info,
monitor_set_auto_start, monitor_set_elevate_on_launch, monitor_set_hardware_config,
monitor_start, monitor_start_elevated, monitor_status, monitor_stop, MonitorKernel,
};
use network_monitor::network_status;
use osd_window::{
@@ -54,8 +54,9 @@ use screenshot::commands::{
screenshot_crop_stored, screenshot_cursor_pos, screenshot_delete_cache,
screenshot_disable_transitions, screenshot_enum_windows, screenshot_fullscreen_png,
screenshot_get_editor_image, screenshot_get_fullscreen_bmp, screenshot_load_cache,
screenshot_register_shortcut, screenshot_save_cache, screenshot_save_png,
screenshot_set_editor_image, screenshot_unregister_shortcut, screenshot_window_from_point,
screenshot_register_pin_shortcut, screenshot_register_shortcut, screenshot_save_cache,
screenshot_save_png, screenshot_set_editor_image, screenshot_unregister_pin_shortcut,
screenshot_unregister_shortcut, screenshot_window_from_point,
};
use clipboard::{
ClipboardManager,
@@ -127,13 +128,14 @@ fn export_bindings() {
downloader_get_tasks, downloader_check_url, downloader_add_task, downloader_pause_task,
downloader_resume_task, downloader_remove_task, downloader_get_settings,
downloader_save_settings, downloader_open_dir, downloader_open_url,
// screenshot19,豁免 2get_fullscreen_bmp 返回 ipc::Response、compose_copy 接收 ipc::Request
// screenshot21,豁免 2get_fullscreen_bmp 返回 ipc::Response、compose_copy 接收 ipc::Request
screenshot_disable_transitions, screenshot_register_shortcut,
screenshot_unregister_shortcut, screenshot_capture_fullscreen, screenshot_fullscreen_png,
screenshot_clear_fullscreen, screenshot_crop_stored, screenshot_crop_copy_stored,
screenshot_window_from_point, screenshot_cursor_pos, screenshot_enum_windows,
screenshot_capture_window, screenshot_set_editor_image, screenshot_get_editor_image,
screenshot_copy_image, screenshot_save_png,
screenshot_unregister_shortcut, screenshot_register_pin_shortcut,
screenshot_unregister_pin_shortcut, screenshot_capture_fullscreen,
screenshot_fullscreen_png, screenshot_clear_fullscreen, screenshot_crop_stored,
screenshot_crop_copy_stored, screenshot_window_from_point, screenshot_cursor_pos,
screenshot_enum_windows, screenshot_capture_window, screenshot_set_editor_image,
screenshot_get_editor_image, screenshot_copy_image, screenshot_save_png,
screenshot_save_cache, screenshot_load_cache, screenshot_delete_cache,
])
.export(Typescript::default(), "../src/lib/bindings.ts")
@@ -204,6 +206,8 @@ pub fn run() {
monitor_get_snapshot,
monitor_get_elevate_on_launch,
monitor_set_elevate_on_launch,
monitor_get_auto_start,
monitor_set_auto_start,
monitor_get_hardware_config,
monitor_set_hardware_config,
network_status,
@@ -291,6 +295,8 @@ pub fn run() {
screenshot_delete_cache,
screenshot_register_shortcut,
screenshot_unregister_shortcut,
screenshot_register_pin_shortcut,
screenshot_unregister_pin_shortcut,
screenshot_disable_transitions,
screenshot_compose_copy
])
+1
View File
@@ -14,6 +14,7 @@ mod system_proxy;
mod types;
pub use pseudo::is_pseudo_node;
pub use system_proxy::get_system_proxy_windows;
pub use types::{InstallProgress, KernelInfo, KernelUpdateInfo, ProfileMeta, ProxySettings, ProxyStatus};
pub use commands::{
proxy_activate_profile, proxy_check_kernel_update, proxy_clear_system_proxy, proxy_close_connection,
+2 -2
View File
@@ -38,7 +38,7 @@ pub(crate) fn clear_system_proxy_windows() -> Result<(), String> {
}
#[cfg(windows)]
pub(crate) fn get_system_proxy_windows() -> bool {
pub fn get_system_proxy_windows() -> bool {
use winreg::enums::*;
use winreg::RegKey;
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
@@ -67,7 +67,7 @@ pub(crate) fn clear_system_proxy_windows() -> Result<(), String> {
Err("系统代理仅支持 Windows".into())
}
#[cfg(not(windows))]
pub(crate) fn get_system_proxy_windows() -> bool {
pub fn get_system_proxy_windows() -> bool {
false
}
+80
View File
@@ -132,6 +132,41 @@ pub fn is_thing_elevated() -> bool {
false
}
// ===================== 监控设置持久化 =====================
/// 监控模块设置(存储于 {app_data_dir}/monitor/settings.json
/// 参照代理模块 ProxySettings 的模式,仅包含需要持久化的运行时开关。
#[derive(Serialize, Deserialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct MonitorSettings {
/// 应用启动时自动启动监控内核(默认 false)
#[serde(default)]
pub auto_start: bool,
}
/// 监控设置文件路径: {app_data_dir}/monitor/settings.json
fn settings_path(app_data_dir: &std::path::Path) -> PathBuf {
app_data_dir.join("monitor").join("settings.json")
}
/// 读取监控设置(文件不存在或解析失败时返回默认值)
pub fn load_monitor_settings(app_data_dir: &std::path::Path) -> MonitorSettings {
fs::read_to_string(settings_path(app_data_dir))
.ok()
.and_then(|s| serde_json::from_str::<MonitorSettings>(&s).ok())
.unwrap_or_default()
}
/// 写入监控设置
pub fn save_monitor_settings(app_data_dir: &std::path::Path, settings: &MonitorSettings) -> Result<(), String> {
let path = settings_path(app_data_dir);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).map_err(|e| format!("创建目录失败: {}", e))?;
}
let content = serde_json::to_string_pretty(settings).map_err(|e| format!("序列化设置失败: {}", e))?;
fs::write(path, content).map_err(|e| format!("写入设置失败: {}", e))
}
// ===================== 永久提权标志持久化 =====================
/// 永久提权标志文件路径: {app_data_dir}/monitor/elevate.json
@@ -295,6 +330,32 @@ impl MonitorKernel {
write_elevate_flag(&self.root.join("elevate.json"), enabled)
}
/// 读取监控设置(auto_start 等)
pub fn load_settings(&self) -> MonitorSettings {
load_monitor_settings(&self.root.parent().unwrap_or(&self.root).to_path_buf())
}
/// 写入监控设置
pub fn save_settings(&self, settings: &MonitorSettings) -> Result<(), String> {
save_monitor_settings(&self.root.parent().unwrap_or(&self.root).to_path_buf(), settings)
}
/// 应用启动时检查是否需要自动启动监控内核
pub fn auto_start_on_launch(&self, app: &AppHandle) {
let settings = self.load_settings();
if !settings.auto_start {
return;
}
let monitor = self.clone();
let app_handle = app.clone();
tauri::async_runtime::spawn(async move {
match monitor.start_with_subscription(&app_handle).await {
Ok(info) => crate::logger::log_info("monitor", &format!("自动启动成功, pid={:?}", info.pid)),
Err(e) => crate::logger::log_warn("monitor", &format!("自动启动跳过: {}", e)),
}
});
}
fn cores_dir(&self) -> PathBuf {
self.root.join("cores")
}
@@ -950,6 +1011,25 @@ pub fn monitor_set_elevate_on_launch(
state.set_elevate_on_launch(enabled)
}
/// 查询"应用启动时自动启动监控内核"是否已启用
#[tauri::command]
pub fn monitor_get_auto_start(
state: tauri::State<'_, MonitorKernel>,
) -> bool {
state.load_settings().auto_start
}
/// 设置/清除"应用启动时自动启动监控内核"开关
#[tauri::command]
pub fn monitor_set_auto_start(
state: tauri::State<'_, MonitorKernel>,
enabled: bool,
) -> Result<(), String> {
let mut settings = state.load_settings();
settings.auto_start = enabled;
state.save_settings(&settings)
}
/// 查询硬件监控配置(透传 Kernel GET /config/hardware)。
/// 返回当前配置 + 可用硬件/传感器类型清单,供前端 Dialog 渲染。
#[tauri::command]
+8 -1
View File
@@ -114,6 +114,8 @@ pub async fn quickpanel_build_file_index(app: AppHandle) -> Result<i64, String>
} else {
settings.index_dirs
};
// 懒加载:首次构建时自动初始化 DB 连接
file_index::ensure_initialized(&app);
// 阻塞操作放到 spawn_blocking
tauri::async_runtime::spawn_blocking(move || file_index::build_index(&dirs))
.await
@@ -126,7 +128,10 @@ pub async fn quickpanel_build_file_index(app: AppHandle) -> Result<i64, String>
pub async fn quickpanel_search_files(
query: String,
limit: Option<i64>,
app: AppHandle,
) -> Result<Vec<file_index::FileRecord>, String> {
// 懒加载:首次搜索时自动初始化 DB 连接
file_index::ensure_initialized(&app);
tauri::async_runtime::spawn_blocking(move || file_index::search(&query, limit.unwrap_or(50)))
.await
.map_err(|e| format!("搜索任务失败: {}", e))
@@ -135,7 +140,9 @@ pub async fn quickpanel_search_files(
/// 获取索引状态
#[tauri::command]
#[specta::specta]
pub fn quickpanel_file_index_stats() -> file_index::IndexStats {
pub async fn quickpanel_file_index_stats(app: AppHandle) -> file_index::IndexStats {
// 懒加载:查询状态前确保 DB 已初始化(未初始化时 stats 返回全 0)
file_index::ensure_initialized(&app);
file_index::stats()
}
+11
View File
@@ -93,6 +93,17 @@ pub fn init(app: &AppHandle) {
crate::logger::log_info("quickpanel", &format!("文件索引 DB 已就绪: {}", path.display()));
}
/// 懒加载:首次访问文件索引时自动初始化(若尚未初始化)。
/// 避免应用启动时即打开 SQLite 连接,降低启动 IO 开销。
pub fn ensure_initialized(app: &AppHandle) {
let guard = index_slot().lock().unwrap_or_else(|e| e.into_inner());
if guard.is_some() {
return;
}
drop(guard);
init(app);
}
/// 判断索引是否已初始化
fn with_conn<F, R>(f: F) -> Option<R>
where
+26 -5
View File
@@ -67,6 +67,27 @@ pub async fn screenshot_unregister_shortcut(app: tauri::AppHandle) -> Result<(),
Ok(())
}
/// 注册(或切换)贴图全局快捷键。传入空字符串则禁用快捷键。
/// 按下时 emit 'screenshot-pin-shortcut',由前端切换贴图窗口显示/隐藏。
#[tauri::command]
#[specta::specta]
pub async fn screenshot_register_pin_shortcut(
app: tauri::AppHandle,
shortcut: String,
) -> Result<(), String> {
crate::shortcut::register_shortcut(&app, "贴图", &shortcut, |a| {
let _ = a.emit(crate::constants::events::SCREENSHOT_PIN_SHORTCUT, ());
})
}
/// 注销贴图全局快捷键
#[tauri::command]
#[specta::specta]
pub async fn screenshot_unregister_pin_shortcut(app: tauri::AppHandle) -> Result<(), String> {
crate::shortcut::unregister_shortcut(&app, "贴图");
Ok(())
}
/// 捕获整个虚拟屏(多显示器拼接)存入静态,不做 PNG 编码
#[tauri::command]
#[specta::specta]
@@ -349,17 +370,17 @@ pub async fn screenshot_save_png(png_base64: String, path: String) -> Result<(),
.map_err(|e| format!("保存任务失败: {}", e))?
}
// ===== 截图历史缓存:完整 PNG 落盘缓存目录,内存只保留缩略图 =====
// ===== 截图历史缓存:完整 PNG 落盘到应用数据目录(持久化,随历史保留数量清理),内存只保留缩略图 =====
/// 历史缓存根目录(app_cache_dir/screenshot/history
/// 历史根目录(app_data_dir/screenshot/history
fn history_cache_dir(app: &tauri::AppHandle) -> Result<std::path::PathBuf, String> {
let dir = app
.path()
.app_cache_dir()
.map_err(|e| format!("获取缓存目录失败: {}", e))?
.app_data_dir()
.map_err(|e| format!("获取应用数据目录失败: {}", e))?
.join("screenshot")
.join("history");
std::fs::create_dir_all(&dir).map_err(|e| format!("创建缓存目录失败: {}", e))?;
std::fs::create_dir_all(&dir).map_err(|e| format!("创建历史目录失败: {}", e))?;
Ok(dir)
}
+5 -12
View File
@@ -90,8 +90,10 @@ pub fn init(app: &mut App<Wry>) -> Result<(), Box<dyn std::error::Error>> {
}
app.manage(clipboard);
// ===== 快速面板:快捷键 + 预创建弹窗 + 文件索引 =====
// ===== 快速面板:快捷键 + 预创建弹窗 =====
// defaultEnabled:true 假设启用;用户在设置页禁用模块时由前端 onDisable 钩子注销快捷键。
// 文件索引 DB 连接改为懒加载(首次搜索/构建时由 commands 中的 ensure_initialized 触发),
// 避免应用启动时即打开 SQLite 连接,降低启动 IO 开销。
let qp_settings = crate::quickpanel::load_settings(&app.handle());
if !qp_settings.shortcut.trim().is_empty() {
let app_handle = app.handle().clone();
@@ -106,8 +108,6 @@ pub fn init(app: &mut App<Wry>) -> Result<(), Box<dyn std::error::Error>> {
// 预创建弹窗窗口(隐藏),首次按快捷键时直接 show,避免首次创建时序问题
crate::quickpanel::ensure_window(&app_handle);
}
// 初始化文件索引数据库(不立即构建,由前端设置页或首次唤起时触发)
crate::quickpanel::file_index::init(&app.handle());
// ===== 托盘菜单 =====
crate::tray_menu::create_tray_menu(app.handle())?;
@@ -123,16 +123,9 @@ pub fn init(app: &mut App<Wry>) -> Result<(), Box<dyn std::error::Error>> {
}
}
// monitor Kernel硬件监控默认启用,被动读取无副作用
// monitor Kernel用户在设置中开启"自动启动"时随应用启动
if let Some(monitor) = app.try_state::<MonitorKernel>() {
let monitor = monitor.inner().clone();
let app_handle = app.handle().clone();
tauri::async_runtime::spawn(async move {
match monitor.start_with_subscription(&app_handle).await {
Ok(info) => crate::logger::log_info("monitor", &format!("自动启动成功, pid={:?}", info.pid)),
Err(e) => crate::logger::log_warn("monitor", &format!("自动启动跳过: {}", e)),
}
});
monitor.auto_start_on_launch(app.handle());
}
// 截图快捷键由前端 screenshotStore 启动时调用 screenshot_register_shortcut 注册
+19
View File
@@ -46,6 +46,7 @@ pub struct ProxyNodeInfo {
#[serde(rename_all = "camelCase")]
pub struct TrayMenuState {
pub proxy_running: bool,
pub system_proxy: bool,
pub monitor_running: bool,
pub proxy_group: Option<String>,
pub proxy_nodes: Vec<ProxyNodeInfo>,
@@ -150,6 +151,8 @@ async fn fetch_proxy_nodes(app: &AppHandle) -> Option<(String, Vec<(String, Opti
pub async fn get_tray_menu_state(app: &AppHandle) -> TrayMenuState {
let proxy_running = is_proxy_running(app);
let monitor_running = is_monitor_running(app);
// 读取 Windows 注册表中的真实系统代理状态(不依赖 settings.json 缓存)
let system_proxy = crate::mihomo_manager::get_system_proxy_windows();
let (proxy_group, proxy_nodes, proxy_current) = if proxy_running {
match fetch_proxy_nodes(app).await {
@@ -172,6 +175,7 @@ pub async fn get_tray_menu_state(app: &AppHandle) -> TrayMenuState {
TrayMenuState {
proxy_running,
system_proxy,
monitor_running,
proxy_group,
proxy_nodes,
@@ -338,6 +342,21 @@ pub async fn tray_menu_action(
}
}
}
"system_proxy_toggle" => {
// 根据注册表当前真实状态切换(不依赖 settings.json 缓存,避免与主界面不同步)
let mihomo = app.state::<MihomoManager>();
if crate::mihomo_manager::get_system_proxy_windows() {
if let Err(e) = mihomo.disable_system_proxy() {
crate::logger::log_error("tray", &format!("关闭系统代理失败: {}", e));
send_notification(&app, "系统代理关闭失败", &e);
}
} else {
if let Err(e) = mihomo.enable_system_proxy() {
crate::logger::log_error("tray", &format!("开启系统代理失败: {}", e));
send_notification(&app, "系统代理开启失败", &e);
}
}
}
"osd_toggle" => {
let _ = app.emit(crate::constants::events::TRAY_TOGGLE_OSD, ());
}