From e2201b87ddd1674c8da485bc8169d8b3e7771f82 Mon Sep 17 00:00:00 2001 From: cyq <15000851237@163.com> Date: Tue, 2 Jun 2026 04:44:57 +0800 Subject: [PATCH] fix(tui): read Wayland clipboard via wl-paste --- crates/tui/src/tui/clipboard.rs | 52 +++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/crates/tui/src/tui/clipboard.rs b/crates/tui/src/tui/clipboard.rs index d0f83934..82b4a241 100644 --- a/crates/tui/src/tui/clipboard.rs +++ b/crates/tui/src/tui/clipboard.rs @@ -10,9 +10,12 @@ #[cfg(not(test))] use std::io::{self, IsTerminal, Write}; use std::path::{Path, PathBuf}; -#[cfg(all( - any(target_os = "macos", target_os = "windows", target_os = "linux"), - not(test) +#[cfg(any( + all(test, unix), + all( + any(target_os = "macos", target_os = "windows", target_os = "linux"), + not(test) + ) ))] use std::process::{Command, Stdio}; use std::time::{SystemTime, UNIX_EPOCH}; @@ -107,6 +110,11 @@ impl ClipboardHandler { /// `workspace` is used as a fallback location when `~/.codewhale/` cannot /// be resolved (e.g. running with a stripped HOME in CI sandboxes). pub fn read(&mut self, workspace: &Path) -> Option { + #[cfg(all(target_os = "linux", not(test)))] + if let Ok(text) = read_text_with_wlpaste() { + return Some(ClipboardContent::Text(text)); + } + self.ensure_clipboard(); let clipboard = self.clipboard.as_mut()?; if let Ok(text) = clipboard.get_text() { @@ -212,6 +220,26 @@ fn write_text_with_wlcopy(text: &str) -> Result<()> { write_text_with_wlcopy_using_argv("wl-copy", text) } +#[cfg(all(target_os = "linux", not(test)))] +fn read_text_with_wlpaste() -> Result { + read_text_with_wlpaste_using_argv("wl-paste") +} + +#[cfg(any(all(test, unix), target_os = "linux"))] +fn read_text_with_wlpaste_using_argv(program: &str) -> Result { + let output = Command::new(program) + .arg("--type") + .arg("text/plain") + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .output() + .map_err(|e| anyhow::anyhow!("Failed to run {program}: {e}"))?; + if !output.status.success() { + bail!("{program} exited with {}", output.status); + } + String::from_utf8(output.stdout).context("wl-paste returned non-UTF-8 text") +} + #[cfg(all(target_os = "linux", not(test)))] fn write_text_with_wlcopy_using_argv(program: &str, text: &str) -> Result<()> { let mut child = Command::new(program) @@ -332,6 +360,8 @@ fn save_image_as_png_in(dir: &Path, image: &ImageData) -> Result { mod tests { use super::*; use std::borrow::Cow; + #[cfg(unix)] + use std::os::unix::fs::PermissionsExt; fn solid_rgba(width: u16, height: u16, rgba: [u8; 4]) -> ImageData<'static> { let mut bytes = Vec::with_capacity((width as usize) * (height as usize) * 4); @@ -419,4 +449,20 @@ mod tests { "unexpected error: {err}" ); } + + #[cfg(unix)] + #[test] + fn wl_paste_helper_reads_text_from_stdout() { + let dir = tempfile::tempdir().unwrap(); + let script = dir.path().join("wl-paste"); + std::fs::write(&script, "#!/bin/sh\nprintf 'from-wayland'\n").unwrap(); + let mut perms = std::fs::metadata(&script).unwrap().permissions(); + perms.set_mode(0o755); + std::fs::set_permissions(&script, perms).unwrap(); + + let text = read_text_with_wlpaste_using_argv(script.to_str().unwrap()) + .expect("read text through wl-paste helper"); + + assert_eq!(text, "from-wayland"); + } }