接入 Tauri 桌面剪贴板能力
新增 Tauri clipboard-manager 依赖并通过 HostBridge 写入系统剪贴板 同步桌面壳能力清单和真实品牌图标配置 补充桌面壳配置守门、测试和宿主壳文档记忆
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
use tauri_plugin_clipboard_manager::ClipboardExt;
|
||||
use tauri_plugin_opener::OpenerExt;
|
||||
|
||||
const HOST_BRIDGE_PROTOCOL: &str = "GenarrativeHostBridge";
|
||||
@@ -57,7 +58,11 @@ fn desktop_platform() -> &'static str {
|
||||
}
|
||||
|
||||
fn capabilities() -> Vec<&'static str> {
|
||||
vec!["host.getRuntime", "app.openExternalUrl"]
|
||||
vec![
|
||||
"host.getRuntime",
|
||||
"app.openExternalUrl",
|
||||
"clipboard.writeText",
|
||||
]
|
||||
}
|
||||
|
||||
fn ok(id: String, result: Value) -> HostBridgeResponse {
|
||||
@@ -97,6 +102,24 @@ fn validate_request(request: &HostBridgeRequest) -> Option<HostBridgeResponse> {
|
||||
None
|
||||
}
|
||||
|
||||
fn required_string_payload<'a>(
|
||||
request: &'a HostBridgeRequest,
|
||||
field: &'static str,
|
||||
) -> Result<&'a str, HostBridgeResponse> {
|
||||
request
|
||||
.payload
|
||||
.as_ref()
|
||||
.and_then(|value| value.get(field))
|
||||
.and_then(Value::as_str)
|
||||
.ok_or_else(|| {
|
||||
failed(
|
||||
request.id.clone(),
|
||||
"invalid_request",
|
||||
format!("{} is required", field),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_host_bridge_request(request: HostBridgeRequest) -> HostBridgeResponse {
|
||||
if let Some(response) = validate_request(&request) {
|
||||
return response;
|
||||
@@ -132,13 +155,9 @@ async fn host_bridge_request(
|
||||
|
||||
match request.method.as_str() {
|
||||
"app.openExternalUrl" => {
|
||||
let Some(url) = request
|
||||
.payload
|
||||
.as_ref()
|
||||
.and_then(|value| value.get("url"))
|
||||
.and_then(Value::as_str)
|
||||
else {
|
||||
return failed(request.id, "invalid_request", "url is required");
|
||||
let url = match required_string_payload(&request, "url") {
|
||||
Ok(url) => url,
|
||||
Err(response) => return response,
|
||||
};
|
||||
|
||||
match app.opener().open_url(url, None::<&str>) {
|
||||
@@ -146,12 +165,24 @@ async fn host_bridge_request(
|
||||
Err(error) => failed(request.id, "host_error", error.to_string()),
|
||||
}
|
||||
}
|
||||
"clipboard.writeText" => {
|
||||
let text = match required_string_payload(&request, "text") {
|
||||
Ok(text) => text,
|
||||
Err(response) => return response,
|
||||
};
|
||||
|
||||
match app.clipboard().write_text(text) {
|
||||
Ok(()) => ok(request.id, json!(true)),
|
||||
Err(error) => failed(request.id, "host_error", error.to_string()),
|
||||
}
|
||||
}
|
||||
_ => resolve_host_bridge_request(request),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.setup(|app| {
|
||||
let window_config = app.config().app.windows.get(0).cloned();
|
||||
@@ -210,4 +241,17 @@ mod tests {
|
||||
assert!(!response.ok);
|
||||
assert_eq!(response.error.expect("error").code, "invalid_request");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_string_payload_is_rejected() {
|
||||
let mut invalid = request("clipboard.writeText");
|
||||
invalid.payload = Some(json!({ "text": 123 }));
|
||||
|
||||
let response = required_string_payload(&invalid, "text").expect_err("invalid payload");
|
||||
|
||||
assert!(!response.ok);
|
||||
let error = response.error.expect("error");
|
||||
assert_eq!(error.code, "invalid_request");
|
||||
assert_eq!(error.message, "text is required");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user