Files
endorphin-tessera-gui/src/shell/clipboard.rs
T

64 lines
2.7 KiB
Rust

//! 把 UTF-8 文本读写适配到当前平台的系统剪贴板命令。
//! 外部命令失败作为普通错误交给调用方处理;这里不解析或执行剪贴板中的程序文本。
//!
//! Clipboard text is always UTF-8, including Finder/LaunchServices launches without LANG.
use anyhow::{Result, bail};
#[cfg(target_os = "macos")]
/// 构造固定 UTF-8 环境的系统命令;本函数不启动进程,退出状态由 read/write 检查。
fn command(program: &str) -> std::process::Command {
let mut cmd = std::process::Command::new(program);
// 从 Finder 等无 LANG 的环境启动时也显式指定 UTF-8,防止系统工具按其他编码转换中文。
cmd.env("LC_ALL", "en_US.UTF-8")
.env("LC_CTYPE", "en_US.UTF-8")
.env("LANG", "en_US.UTF-8");
cmd
}
/// 取得系统剪贴板的 UTF-8 文本,失败作为可显示错误返回。
pub fn read() -> Result<String> {
#[cfg(target_os = "macos")]
{
// 直接启动固定路径程序并传递独立参数,不经过 shell;-Prefer txt 要求文本表示。
let output = command("/usr/bin/pbpaste")
.arg("-Prefer")
.arg("txt")
.output()?;
if !output.status.success() {
bail!("system clipboard read failed");
}
// Never silently replace invalid bytes in the user's source.
Ok(String::from_utf8(output.stdout)?)
}
#[cfg(not(target_os = "macos"))]
{
bail!("system clipboard adapter is not available on this platform")
}
}
/// 将提供的文本交给系统剪贴板命令,不执行文本内容。
pub fn write(value: &str) -> Result<()> {
#[cfg(target_os = "macos")]
{
use std::io::Write;
// 正文只经子进程标准输入传递,不拼成命令参数,因此引号、换行和源码中的命令字符保持原意。
let mut child = command("/usr/bin/pbcopy")
.stdin(std::process::Stdio::piped())
.spawn()?;
let mut stdin = child
.stdin
.take()
.ok_or_else(|| anyhow::anyhow!("clipboard stdin is unavailable"))?;
stdin.write_all(value.as_bytes())?;
// 写完后先关闭管道发送 EOF,让 pbcopy 知道输入结束;之后等待退出状态,不能仅以写管道成功作为完成。
drop(stdin);
if !child.wait()?.success() {
bail!("system clipboard write failed");
}
Ok(())
}
#[cfg(not(target_os = "macos"))]
{
// 非 macOS 当前没有实现适配器,显式报错而不伪装成写入成功。
let _ = value;
bail!("system clipboard adapter is not available on this platform")
}
}