macos 上的全屏
如果你在 windows 上或者 linux 上使用 "fullscreen": true,或许没什么问题。然而不出意外的话,在截图这个功能上,macOS 是要出意外的。
之前我们讲过,截图软件本质上就是一个透明的,置顶的,全屏的应用。
置顶在 macOS 上不会有问题。
透明在 macOS 上需要开启 "macOSPrivateApi": true 属性并在 crate 上要开启以下 features:
tauri = { version = "2", features = ["macos-private-api"] }
而全屏也不能使用 "fullscreen": true属性。因为 macOS 上的全屏,会单独地自动在第二个桌面空间进行全屏。
而是要使用"maximized": true。我们应该在应用加载完后,把应用设置最大化:
// 页面加载后将窗口最大化
useEffect(() => {
const maximizeWindow = async () => {
const currentWindow = getCurrentWebviewWindow();
await currentWindow.maximize();
};
maximizeWindow();
}, []);
复制截图到剪切板
首先,前端是有复制图片到剪切板功能的,这个 api 基本现代浏览器应该都支持,api 如下:
const blob = new Blob([new Uint8Array(image).buffer], { type: "image/png" });
const clipboardItem = new ClipboardItem({ 'image/png': blob })
await navigator.clipboard.write([clipboardItem])
如果你真的使用上面的 api,则会得到下面的错误:
NotAllowedError: The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission
原因是前端的 API 需要在安全上下文(HTTPS或localhost)中,并且调用时需要明确获得用户的许可。
第二种方法是通过剪切板插件clipboard-manager:
pnpm tauri add clipboard-manager
import { Image as TauriImage } from "@tauri-apps/api/image";
const image: Uint8Array = await invoke("capture", {
x: selectionRect.x,
y: selectionRect.y,
width: selectionRect.width,
height: selectionRect.height
});
// 写入剪贴板
await writeImage(image);
但是,在 macOS 上,你也有可能会得到一个 bug:
thread 'tokio-runtime-worker' panicked at /Users/xxx/.cargo/registry/src/rsproxy.cn-e3de039b2554c837/arboard-3.6.1/src/platform/osx.rs:88:6:
called `Option::unwrap()` on a `None` value
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
clipboard-manager 依赖 arboard 这个 crate 实现剪切版功能,而 arboard 这个 crate 在 macOS 平台上调用的是 cocoa 框架的 API。不幸的是, arboard 在这里直接来了个 unwrap:
let cg_image = unsafe {
CGImageCreate(
width,
height,
8,
32,
4 * width,
Some(&colorspace),
CGBitmapInfo::ByteOrderDefault | CGBitmapInfo(CGImageAlphaInfo::Last.0),
Some(&provider),
ptr::null_mut(),
false,
CGColorRenderingIntent::RenderingIntentDefault,
)
}
.unwrap();
目前 arboard 已经修改了这行代码,改为返回Error了.
那如何知道这个 bug 具体的原因是什么? 答案是 console.app 这个应用(中文又叫控制台)。通过打开控制台,点击开始记录日志,再复现出现 bug 的操作,我们就能得到上面的错误日志了。
如果你直接把二进制图片写进剪切板:
// image 是二进制文件数据
const img_data = await TauriImage.new(image, selectionRect.width, selectionRect.height)
await writeImage(img_data);
// 或者直接 writeImage(image);
大概会在控制台里找到这么一个错误日志:
CGImageCreate: invalid image data size: 165 (height) x 836 (bytesPerRow) data provider size 27295
在 windows 系统上,这个错误会直接显示出来。
按官方的示例,我们还可能碰到这个错误:
Unhandled Promise Rejection: expected RGBA image data, found raw bytes
事实上,绕这么一大圈,这仅仅是官方文档的问题。它在插件项里没有提到,只是在 javascript api 文档里提到了,tauri 需要添加image-ico 或 image-png features:
[dependencies]
tauri = { version = "...", features = ["...", "image-png"] }
保存截图
没什么可说的,直接使用 dialog 调出保存到文件拿到文件路径,再传到 rust 层保存即可。
import { save } from '@tauri-apps/plugin-dialog';
const path = await save({
filters: [
{
name: 'screenshot',
extensions: ['png', 'jpeg'],
},
],
});
try {
await invoke("capture", {
x: selectionRect.x,
y: selectionRect.y,
width: selectionRect.width,
height: selectionRect.height,
savePath: path,
});
const currentWindow = getCurrentWebviewWindow();
await currentWindow.close();
} catch (error) {
console.error("关闭窗口时出错:", error);
}
快捷键
截图应用是不应该一直顶置的,所以我们需要截图后把主窗口隐藏掉,当需要时再调用出来,这时候需要实现快捷键唤醒功能。
pnpm tauri add global-shortcut
需要注意的是,上面的命令会自动在 lib.rs添加这么一行代码:
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
由于我们需要在 rust 层实现快捷键唤醒,所以需要把这行去掉,重新实现。
比如我们要实现一个ctrl + shift + s (mac: command + shift + s)截图功能,在响应回调里实现显示主窗口。
.setup(|app| {
#[cfg(desktop)]
{
use tauri::Manager;
use tauri_plugin_global_shortcut::{Code, GlobalShortcutExt, Modifiers, Shortcut};
let ctrl_shift_s_shortcut =
Shortcut::new(Some(Modifiers::SUPER | Modifiers::SHIFT), Code::KeyS);
app.handle().plugin(
tauri_plugin_global_shortcut::Builder::new()
.with_handler(move |app, shortcut, _event| {
let app_handle = app.clone(); // 克隆 handle 用于闭包
println!("{:?}", shortcut);
if shortcut == &ctrl_shift_s_shortcut {
println!("Ctrl-Shift-S Detected!");
app_handle
.get_webview_window("main")
.unwrap()
.show()
.unwrap();
}
})
.build(),
)?;
app.global_shortcut().register(ctrl_shift_s_shortcut)?;
}
Ok(())
})
其他
为什么截图预览在 tauri 上会闪?
这是由于我们使用定时器去调用 rust 层对鼠标预览区进行截图来实现的,定时器的间隔越大,这个预览功能更闪。如果我们能把这个功能的整体耗时优化到 20ms 以内一帧,就大体上不会看到闪的现象了。
据我观察,在我的设备上, 100x100 大小的预览截图大概在 5ms-15ms 之间。这个截图耗时加上传输的延迟耗时应该是够用了。这也是为什么说Tauri 写截图应用其实不太占优势的原因之一,最好还是原生框架效果高一些。
感兴趣的话,完整代码在这里:点我直达