744b78a1b3
桌面壳剪贴板失败只记录固定阶段标签 扩展桌面壳配置门禁禁止剪贴板日志输出插件错误细节 补充宿主壳方案和共享决策中的剪贴板诊断边界
166 lines
4.9 KiB
Rust
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"));
|
|
}
|
|
}
|