Files
Genarrative/apps/desktop-shell/src-tauri/src/host_bridge/clipboard.rs
T
kdletters 744b78a1b3 收口桌面壳剪贴板诊断日志
桌面壳剪贴板失败只记录固定阶段标签

扩展桌面壳配置门禁禁止剪贴板日志输出插件错误细节

补充宿主壳方案和共享决策中的剪贴板诊断边界
2026-06-21 05:13:44 +08:00

166 lines
4.9 KiB
Rust

use tauri_plugin_clipboard_manager::ClipboardExt;
use crate::host_bridge::protocol::{
failed, ok, required_string_payload, HostBridgeRequest, HostBridgeResponse,
};
use serde_json::json;
pub(crate) const CLIPBOARD_TEXT_MAX_LENGTH: usize = 100000;
pub(crate) fn normalize_clipboard_text(text: &str) -> String {
text.chars().take(CLIPBOARD_TEXT_MAX_LENGTH).collect()
}
pub(crate) fn write_desktop_clipboard_text(
app: &tauri::AppHandle,
text: &str,
) -> Result<(), String> {
app.clipboard()
.write_text(normalize_clipboard_text(text))
.map_err(|error| error.to_string())
}
pub(crate) fn read_desktop_clipboard_text(app: &tauri::AppHandle) -> Result<String, String> {
app.clipboard()
.read_text()
.map(|text| normalize_clipboard_text(&text))
.map_err(|error| error.to_string())
}
fn log_desktop_clipboard_failure(label: &str) -> bool {
eprintln!("desktop clipboard failed for {label}");
false
}
pub(crate) fn clipboard_write_unavailable_response(
request: &HostBridgeRequest,
) -> HostBridgeResponse {
failed(
request.id.clone(),
"host_error",
"clipboard write unavailable",
)
}
pub(crate) fn clipboard_read_unavailable_response(
request: &HostBridgeRequest,
) -> HostBridgeResponse {
failed(
request.id.clone(),
"host_error",
"clipboard read unavailable",
)
}
pub(crate) fn write_desktop_host_bridge_clipboard_text(
app: &tauri::AppHandle,
request: &HostBridgeRequest,
) -> HostBridgeResponse {
let text = match required_string_payload(request, "text") {
Ok(text) => text,
Err(response) => return response,
};
match write_desktop_clipboard_text(app, text) {
Ok(()) => ok(request.id.clone(), json!(true)),
Err(_error) => {
log_desktop_clipboard_failure("write.text");
clipboard_write_unavailable_response(request)
}
}
}
pub(crate) fn read_desktop_host_bridge_clipboard_text(
app: &tauri::AppHandle,
request: &HostBridgeRequest,
) -> HostBridgeResponse {
match read_desktop_clipboard_text(app) {
Ok(text) => ok(
request.id.clone(),
json!({
"text": text,
}),
),
Err(_error) => {
log_desktop_clipboard_failure("read.text");
clipboard_read_unavailable_response(request)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clipboard_text_is_truncated_to_contract_limit() {
assert_eq!(normalize_clipboard_text("作品号 PZ-1"), "作品号 PZ-1");
assert_eq!(
normalize_clipboard_text(&"a".repeat(CLIPBOARD_TEXT_MAX_LENGTH + 10)).len(),
CLIPBOARD_TEXT_MAX_LENGTH
);
let unicode_text = "猫".repeat(CLIPBOARD_TEXT_MAX_LENGTH + 10);
let truncated = normalize_clipboard_text(&unicode_text);
assert_eq!(truncated.chars().count(), CLIPBOARD_TEXT_MAX_LENGTH);
assert!(truncated.ends_with('猫'));
}
#[test]
fn clipboard_read_response_reports_contract_shape() {
let response = ok(
"clipboard-read".to_string(),
json!({
"text": normalize_clipboard_text("邀请码 GEN-1")
}),
);
assert!(response.ok);
assert_eq!(
response.result.expect("clipboard result"),
json!({
"text": "邀请码 GEN-1"
})
);
}
#[test]
fn clipboard_unavailable_responses_are_stable() {
let write_response = clipboard_write_unavailable_response(&HostBridgeRequest {
bridge: "GenarrativeHostBridge".to_string(),
version: 1,
id: "clipboard-write".to_string(),
method: "clipboard.writeText".to_string(),
payload: None,
});
let read_response = clipboard_read_unavailable_response(&HostBridgeRequest {
bridge: "GenarrativeHostBridge".to_string(),
version: 1,
id: "clipboard-read".to_string(),
method: "clipboard.readText".to_string(),
payload: None,
});
assert!(!write_response.ok);
let write_error = write_response.error.expect("clipboard write error");
assert_eq!(write_error.code, "host_error");
assert_eq!(write_error.message, "clipboard write unavailable");
assert!(!read_response.ok);
let read_error = read_response.error.expect("clipboard read error");
assert_eq!(read_error.code, "host_error");
assert_eq!(read_error.message, "clipboard read unavailable");
}
#[test]
fn clipboard_failures_are_logged_without_exposing_native_detail() {
assert!(!log_desktop_clipboard_failure("write.text"));
assert!(!log_desktop_clipboard_failure("read.text"));
}
#[test]
fn clipboard_failures_log_stable_label_only() {
assert!(!log_desktop_clipboard_failure("write.text"));
}
}