性能优化
This commit is contained in:
@@ -15,10 +15,11 @@
|
||||
//! - screenshot_disable_transitions:禁用窗口显示/隐藏过渡动画(消除覆盖层缩放动画)
|
||||
|
||||
use super::{CaptureData, WindowInfo};
|
||||
use tauri::Manager;
|
||||
use tauri::{Emitter, Manager};
|
||||
|
||||
/// 禁用指定窗口(按 label 查找)的显示/隐藏过渡动画,消除覆盖层出现/消失时的缩放动画
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn screenshot_disable_transitions(
|
||||
app: tauri::AppHandle,
|
||||
label: String,
|
||||
@@ -48,22 +49,27 @@ pub async fn screenshot_disable_transitions(
|
||||
|
||||
/// 注册(或切换)截图全局快捷键。传入空字符串则禁用快捷键。
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn screenshot_register_shortcut(
|
||||
app: tauri::AppHandle,
|
||||
shortcut: String,
|
||||
) -> Result<(), String> {
|
||||
super::shortcut::register_shortcut(&app, &shortcut)
|
||||
crate::shortcut::register_shortcut(&app, "截图", &shortcut, |a| {
|
||||
let _ = a.emit(crate::constants::events::SCREENSHOT_SHORTCUT, ());
|
||||
})
|
||||
}
|
||||
|
||||
/// 注销截图全局快捷键
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn screenshot_unregister_shortcut(app: tauri::AppHandle) -> Result<(), String> {
|
||||
super::shortcut::unregister_shortcut(&app);
|
||||
crate::shortcut::unregister_shortcut(&app, "截图");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 捕获整个虚拟屏(多显示器拼接)存入静态,不做 PNG 编码
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn screenshot_capture_fullscreen() -> Result<(), String> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
@@ -101,6 +107,7 @@ pub async fn screenshot_get_fullscreen_bmp() -> Result<tauri::ipc::Response, Str
|
||||
|
||||
/// 全屏捕获编码为 PNG base64 并清除(全屏截图直接进编辑器)
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn screenshot_fullscreen_png() -> Result<CaptureData, String> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
@@ -116,6 +123,7 @@ pub async fn screenshot_fullscreen_png() -> Result<CaptureData, String> {
|
||||
|
||||
/// 清除静态全屏捕获(覆盖层关闭/取消时释放内存)
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn screenshot_clear_fullscreen() -> Result<(), String> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
@@ -130,6 +138,7 @@ pub async fn screenshot_clear_fullscreen() -> Result<(), String> {
|
||||
|
||||
/// 按物理像素坐标裁剪已存储的全屏捕获
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn screenshot_crop_stored(
|
||||
x: i32,
|
||||
y: i32,
|
||||
@@ -153,6 +162,7 @@ pub async fn screenshot_crop_stored(
|
||||
|
||||
/// 裁剪已存储的全屏捕获并直接写入剪贴板(一次 IPC 完成"裁剪+复制")
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn screenshot_crop_copy_stored(
|
||||
x: i32,
|
||||
y: i32,
|
||||
@@ -176,6 +186,7 @@ pub async fn screenshot_crop_copy_stored(
|
||||
|
||||
/// 拾取指定物理屏幕坐标下的顶层窗口
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn screenshot_window_from_point(
|
||||
x: i32,
|
||||
y: i32,
|
||||
@@ -197,6 +208,7 @@ pub async fn screenshot_window_from_point(
|
||||
|
||||
/// 获取当前鼠标物理屏幕坐标(覆盖层打开时定位初始悬停窗口)
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn screenshot_cursor_pos() -> Result<(i32, i32), String> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
@@ -212,6 +224,7 @@ pub async fn screenshot_cursor_pos() -> Result<(i32, i32), String> {
|
||||
|
||||
/// 枚举所有可见顶层窗口
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn screenshot_enum_windows() -> Result<Vec<WindowInfo>, String> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
@@ -227,6 +240,7 @@ pub async fn screenshot_enum_windows() -> Result<Vec<WindowInfo>, String> {
|
||||
|
||||
/// 按 hwnd 捕获指定窗口
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn screenshot_capture_window(hwnd: isize) -> Result<CaptureData, String> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
@@ -250,6 +264,7 @@ pub async fn screenshot_capture_window(hwnd: isize) -> Result<CaptureData, Strin
|
||||
|
||||
/// 存入编辑器图片(base64 PNG)
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn screenshot_set_editor_image(png_base64: String) -> Result<(), String> {
|
||||
super::set_editor_image(png_base64);
|
||||
Ok(())
|
||||
@@ -257,12 +272,14 @@ pub async fn screenshot_set_editor_image(png_base64: String) -> Result<(), Strin
|
||||
|
||||
/// 取出编辑器图片(编辑器窗口加载时调用,取出即清除)
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn screenshot_get_editor_image() -> Result<Option<String>, String> {
|
||||
Ok(super::take_editor_image())
|
||||
}
|
||||
|
||||
/// 将 PNG base64 写入系统剪贴板(转 CF_DIB)
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn screenshot_copy_image(png_base64: String) -> Result<(), String> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
@@ -283,6 +300,7 @@ pub async fn screenshot_copy_image(png_base64: String) -> Result<(), String> {
|
||||
///
|
||||
/// 省去前端 toDataURL(PNG 编码+base64) → Rust base64 解码 → PNG 解码 三次往返。
|
||||
/// body 格式:前 8 字节 = width(i32 LE) + height(i32 LE),之后为 raw RGBA 像素。
|
||||
/// 注:参数为 tauri::ipc::Request(原始 body),specta 无法生成,豁免标注。
|
||||
#[tauri::command]
|
||||
pub async fn screenshot_compose_copy(
|
||||
request: tauri::ipc::Request<'_>,
|
||||
@@ -314,6 +332,7 @@ pub async fn screenshot_compose_copy(
|
||||
|
||||
/// 将 PNG base64 写入文件
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn screenshot_save_png(png_base64: String, path: String) -> Result<(), String> {
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
#[cfg(windows)]
|
||||
@@ -329,3 +348,80 @@ pub async fn screenshot_save_png(png_base64: String, path: String) -> Result<(),
|
||||
.await
|
||||
.map_err(|e| format!("保存任务失败: {}", e))?
|
||||
}
|
||||
|
||||
// ===== 截图历史缓存:完整 PNG 落盘缓存目录,内存只保留缩略图 =====
|
||||
|
||||
/// 历史缓存根目录(app_cache_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))?
|
||||
.join("screenshot")
|
||||
.join("history");
|
||||
std::fs::create_dir_all(&dir).map_err(|e| format!("创建缓存目录失败: {}", e))?;
|
||||
Ok(dir)
|
||||
}
|
||||
|
||||
/// 校验 path 属于历史缓存目录(防止路径穿越/任意文件读写)
|
||||
fn ensure_in_history_dir(app: &tauri::AppHandle, path: &str) -> Result<std::path::PathBuf, String> {
|
||||
let dir = history_cache_dir(app)?;
|
||||
let p = std::path::PathBuf::from(path);
|
||||
if !p.starts_with(&dir) {
|
||||
return Err("非法路径:不在截图历史缓存目录内".into());
|
||||
}
|
||||
Ok(p)
|
||||
}
|
||||
|
||||
/// 将完整 PNG 写入历史缓存目录,返回文件路径
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn screenshot_save_cache(
|
||||
app: tauri::AppHandle,
|
||||
png_base64: String,
|
||||
) -> Result<String, String> {
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
let dir = history_cache_dir(&app)?;
|
||||
// 时间戳微秒命名(避免引入额外依赖;并发截图的同微秒碰撞可忽略)
|
||||
let ts = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_micros())
|
||||
.unwrap_or(0);
|
||||
let path = dir.join(format!("{}.png", ts));
|
||||
super::capture::save_png_to_file(&png_base64, &path.to_string_lossy())?;
|
||||
Ok(path.to_string_lossy().into_owned())
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("缓存任务失败: {}", e))?
|
||||
}
|
||||
|
||||
/// 从历史缓存目录读取 PNG 并返回 base64(点击历史项复制/保存时一次性加载,不常驻内存)
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn screenshot_load_cache(
|
||||
app: tauri::AppHandle,
|
||||
path: String,
|
||||
) -> Result<String, String> {
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
let p = ensure_in_history_dir(&app, &path)?;
|
||||
let bytes = std::fs::read(&p).map_err(|e| format!("读取缓存失败: {}", e))?;
|
||||
use base64::Engine as _;
|
||||
Ok(base64::engine::general_purpose::STANDARD.encode(bytes))
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("读取缓存任务失败: {}", e))?
|
||||
}
|
||||
|
||||
/// 删除历史缓存文件(历史项移除/清空时调用,静默忽略不存在文件)
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn screenshot_delete_cache(app: tauri::AppHandle, path: String) -> Result<(), String> {
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
if let Ok(p) = ensure_in_history_dir(&app, &path) {
|
||||
let _ = std::fs::remove_file(p);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("删除缓存任务失败: {}", e))?
|
||||
}
|
||||
|
||||
@@ -5,13 +5,14 @@
|
||||
|
||||
use std::sync::Mutex;
|
||||
|
||||
use specta::Type;
|
||||
|
||||
#[cfg(windows)]
|
||||
pub mod capture;
|
||||
pub mod commands;
|
||||
pub mod shortcut;
|
||||
|
||||
/// 前端可见的捕获数据
|
||||
#[derive(serde::Serialize)]
|
||||
#[derive(serde::Serialize, Type)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CaptureData {
|
||||
pub png_base64: String,
|
||||
@@ -20,7 +21,7 @@ pub struct CaptureData {
|
||||
}
|
||||
|
||||
/// 窗口信息(窗口拾取 / 枚举)
|
||||
#[derive(serde::Serialize)]
|
||||
#[derive(serde::Serialize, Type)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WindowInfo {
|
||||
pub hwnd: isize,
|
||||
@@ -30,7 +31,7 @@ pub struct WindowInfo {
|
||||
pub visual_rect: Option<ScreenRect>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, Clone, Copy)]
|
||||
#[derive(serde::Serialize, Clone, Copy, Type)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScreenRect {
|
||||
pub x: i32,
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
//! 截图全局快捷键:可自定义注册/注销(默认 Ctrl+Alt+A)。
|
||||
//!
|
||||
//! 与剪贴板快捷弹窗(clipboard::popup)的实现一致:
|
||||
//! 用 `on_shortcut` 为每个快捷键绑定独立处理器,切换时先注销旧的再注册新的。
|
||||
//! 按下时 emit `screenshot-shortcut` 事件,前端 store 监听后触发 startCapture。
|
||||
|
||||
use std::sync::Mutex;
|
||||
use tauri::{AppHandle, Emitter};
|
||||
use tauri_plugin_global_shortcut::{GlobalShortcutExt, Shortcut, ShortcutState};
|
||||
|
||||
/// 当前已注册的快捷键字符串(用于切换时注销旧快捷键)
|
||||
static CURRENT_SHORTCUT: Mutex<Option<String>> = Mutex::new(None);
|
||||
|
||||
/// 解析快捷键字符串为 Shortcut(格式如 "Ctrl+Alt+A"、"Shift+PrintScreen")
|
||||
pub fn parse_shortcut(s: &str) -> Option<Shortcut> {
|
||||
s.trim().parse::<Shortcut>().ok()
|
||||
}
|
||||
|
||||
/// 注册全局快捷键。重复调用会先注销旧快捷键。
|
||||
/// 传入空字符串则仅注销不注册(禁用快捷键)。
|
||||
pub fn register_shortcut(app: &AppHandle, shortcut_str: &str) -> Result<(), String> {
|
||||
unregister_shortcut(app);
|
||||
|
||||
if shortcut_str.trim().is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let shortcut = parse_shortcut(shortcut_str)
|
||||
.ok_or_else(|| format!("无效的快捷键: {}", shortcut_str))?;
|
||||
|
||||
let app_handle = app.clone();
|
||||
app.global_shortcut()
|
||||
.on_shortcut(shortcut, move |_app, _shortcut, event| {
|
||||
// 仅在按下时触发(松开不触发)
|
||||
if event.state == ShortcutState::Pressed {
|
||||
let _ = app_handle.emit("screenshot-shortcut", ());
|
||||
}
|
||||
})
|
||||
.map_err(|e| format!("注册快捷键失败: {}", e))?;
|
||||
|
||||
if let Ok(mut cur) = CURRENT_SHORTCUT.lock() {
|
||||
*cur = Some(shortcut_str.to_string());
|
||||
}
|
||||
eprintln!("[screenshot] 已注册快捷键: {}", shortcut_str);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 注销当前快捷键
|
||||
pub fn unregister_shortcut(app: &AppHandle) {
|
||||
if let Ok(cur) = CURRENT_SHORTCUT.lock() {
|
||||
if let Some(ref s) = *cur {
|
||||
if let Some(shortcut) = parse_shortcut(s) {
|
||||
let _ = app.global_shortcut().unregister(shortcut);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Ok(mut cur) = CURRENT_SHORTCUT.lock() {
|
||||
*cur = None;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user