diff --git a/Cargo.lock b/Cargo.lock index 425d6a132..c32c0c0c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -251,6 +251,7 @@ dependencies = [ "parking_lot", "percent-encoding", "windows-sys 0.60.2", + "wl-clipboard-rs", "x11rb", ] @@ -810,7 +811,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ - "nom", + "nom 7.1.3", ] [[package]] @@ -1369,7 +1370,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" dependencies = [ "futures-core", - "nom", + "nom 7.1.3", "pin-project-lite", ] @@ -1411,6 +1412,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flate2" version = "1.1.9" @@ -2770,6 +2777,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + [[package]] name = "ntapi" version = "0.4.3" @@ -3664,6 +3680,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "os_pipe" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "owned_ttf_parser" version = "0.25.1" @@ -3718,6 +3744,17 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap 2.14.0", +] + [[package]] name = "phf" version = "0.11.3" @@ -5501,6 +5538,17 @@ dependencies = [ "syn", ] +[[package]] +name = "tree_magic_mini" +version = "3.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8765b90061cba6c22b5831f675da109ae5561588290f9fa2317adab2714d5a6" +dependencies = [ + "memchr", + "nom 8.0.0", + "petgraph", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -6559,6 +6607,24 @@ dependencies = [ "wasmparser", ] +[[package]] +name = "wl-clipboard-rs" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9651471a32e87d96ef3a127715382b2d11cc7c8bb9822ded8a7cc94072eb0a3" +dependencies = [ + "libc", + "log", + "os_pipe", + "rustix 1.1.4", + "thiserror 2.0.18", + "tree_magic_mini", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-wlr", +] + [[package]] name = "writeable" version = "0.6.3" diff --git a/crates/op-host-desktop/Cargo.toml b/crates/op-host-desktop/Cargo.toml index 56331948c..8119d8c48 100644 --- a/crates/op-host-desktop/Cargo.toml +++ b/crates/op-host-desktop/Cargo.toml @@ -183,10 +183,11 @@ serde = { workspace = true } serde_json = { workspace = true } toml_edit = "0.25" rfd = "0.14" -# OS clipboard for the AI chat input's Cmd+C / Cmd+V / Cmd+X. -# arboard wraps NSPasteboard (macOS), the Win32 clipboard, and -# X11 / Wayland on Linux — one cross-platform text-clipboard API. -arboard = "3" +# OS clipboard for Cmd+C / Cmd+V / Cmd+X, including canvas images. +# Native Wayland access is opt-in in arboard; without this feature a +# Wayland session still probes X11/XWayland and misses clipboard offers +# made directly by apps such as KDE Spectacle. +arboard = { version = "3", features = ["wayland-data-control"] } # Base64-encodes chat image attachments for providers that take # inline image content blocks (Claude SDK) or a base64 body field # (HTTP-server transport). Same pure-Rust crate op-figma uses. diff --git a/crates/op-host-desktop/src/clipboard.rs b/crates/op-host-desktop/src/clipboard.rs index bcee0550b..147cbe96c 100644 --- a/crates/op-host-desktop/src/clipboard.rs +++ b/crates/op-host-desktop/src/clipboard.rs @@ -93,3 +93,34 @@ pub fn set_text(text: &str) { let _ = clipboard.set_text(text.to_owned()); } } + +#[cfg(test)] +mod tests { + use super::*; + use std::borrow::Cow; + + #[test] + fn native_clipboard_rgba_is_encoded_as_png() { + let image = arboard::ImageData { + width: 2, + height: 1, + bytes: Cow::Owned(vec![255, 0, 0, 255, 0, 255, 0, 128]), + }; + + let encoded = encode_image(image).expect("valid clipboard RGBA should encode"); + + assert_eq!((encoded.width, encoded.height), (2, 1)); + assert!(encoded.png.starts_with(b"\x89PNG\r\n\x1a\n")); + } + + #[test] + fn native_clipboard_rgba_rejects_truncated_rows() { + let image = arboard::ImageData { + width: 2, + height: 1, + bytes: Cow::Owned(vec![255, 0, 0, 255]), + }; + + assert!(encode_image(image).is_none()); + } +}