agentmux_cef\commands/
clipboard.rs1pub fn read_clipboard() -> Result<serde_json::Value, String> {
10 let text = read_clipboard_text()?;
11 Ok(serde_json::json!(text))
12}
13
14pub fn write_clipboard(args: &serde_json::Value) -> Result<serde_json::Value, String> {
16 let text = args
17 .get("text")
18 .and_then(|v| v.as_str())
19 .ok_or_else(|| "missing 'text' argument".to_string())?;
20 write_clipboard_text(text)?;
21 Ok(serde_json::Value::Null)
22}
23
24#[cfg(target_os = "windows")]
25fn read_clipboard_text() -> Result<String, String> {
26 use windows_sys::Win32::System::DataExchange::*;
27 use windows_sys::Win32::System::Memory::*;
28 use windows_sys::Win32::System::Ole::CF_UNICODETEXT;
29
30 unsafe {
31 if OpenClipboard(std::ptr::null_mut()) == 0 {
32 return Err("Failed to open clipboard".into());
33 }
34 let handle = GetClipboardData(CF_UNICODETEXT as u32);
35 if handle.is_null() {
36 CloseClipboard();
37 return Ok(String::new());
38 }
39 let ptr = GlobalLock(handle) as *const u16;
40 if ptr.is_null() {
41 CloseClipboard();
42 return Err("Failed to lock clipboard data".into());
43 }
44 let mut len = 0;
45 while *ptr.add(len) != 0 {
46 len += 1;
47 }
48 let slice = std::slice::from_raw_parts(ptr, len);
49 let text = String::from_utf16_lossy(slice);
50 GlobalUnlock(handle);
51 CloseClipboard();
52 Ok(text)
53 }
54}
55
56#[cfg(target_os = "windows")]
57fn write_clipboard_text(text: &str) -> Result<(), String> {
58 use windows_sys::Win32::System::DataExchange::*;
59 use windows_sys::Win32::System::Memory::*;
60 use windows_sys::Win32::Foundation::GlobalFree;
61 use windows_sys::Win32::System::Ole::CF_UNICODETEXT;
62
63 let wide: Vec<u16> = text.encode_utf16().chain(std::iter::once(0)).collect();
64 let size = wide.len() * 2;
65
66 unsafe {
67 let hmem = GlobalAlloc(GMEM_MOVEABLE, size);
68 if hmem.is_null() {
69 return Err("Failed to allocate clipboard memory".into());
70 }
71 let ptr = GlobalLock(hmem) as *mut u16;
72 if ptr.is_null() {
73 GlobalFree(hmem);
74 return Err("Failed to lock clipboard memory".into());
75 }
76 std::ptr::copy_nonoverlapping(wide.as_ptr(), ptr, wide.len());
77 GlobalUnlock(hmem);
78
79 if OpenClipboard(std::ptr::null_mut()) == 0 {
80 GlobalFree(hmem);
81 return Err("Failed to open clipboard".into());
82 }
83 EmptyClipboard();
84 SetClipboardData(CF_UNICODETEXT as u32, hmem);
85 CloseClipboard();
86 Ok(())
87 }
88}
89
90#[cfg(target_os = "macos")]
91fn read_clipboard_text() -> Result<String, String> {
92 use std::process::Command;
93 let output = Command::new("pbpaste")
94 .output()
95 .map_err(|e| format!("pbpaste failed: {}", e))?;
96 Ok(String::from_utf8_lossy(&output.stdout).to_string())
97}
98
99#[cfg(target_os = "macos")]
100fn write_clipboard_text(text: &str) -> Result<(), String> {
101 use std::io::Write;
102 use std::process::{Command, Stdio};
103 let mut child = Command::new("pbcopy")
104 .stdin(Stdio::piped())
105 .spawn()
106 .map_err(|e| format!("pbcopy failed: {}", e))?;
107 child
108 .stdin
109 .as_mut()
110 .unwrap()
111 .write_all(text.as_bytes())
112 .map_err(|e| format!("pbcopy write failed: {}", e))?;
113 child.wait().map_err(|e| format!("pbcopy wait failed: {}", e))?;
114 Ok(())
115}
116
117#[cfg(target_os = "linux")]
118fn is_wayland() -> bool {
119 std::env::var("WAYLAND_DISPLAY").map_or(false, |v| !v.is_empty())
120}
121
122#[cfg(target_os = "linux")]
123fn read_clipboard_text() -> Result<String, String> {
124 use std::process::Command;
125 if is_wayland() {
126 if let Ok(output) = Command::new("wl-paste").args(["--no-newline"]).output() {
127 if output.status.success() {
128 return Ok(String::from_utf8_lossy(&output.stdout).to_string());
129 }
130 }
131 }
132 let output = Command::new("xclip")
134 .args(["-selection", "clipboard", "-o"])
135 .output()
136 .or_else(|_| Command::new("xsel").args(["--clipboard", "--output"]).output())
137 .map_err(|e| format!("clipboard read failed (install wl-paste, xclip, or xsel): {}", e))?;
138 Ok(String::from_utf8_lossy(&output.stdout).to_string())
139}
140
141#[cfg(target_os = "linux")]
142fn write_clipboard_text(text: &str) -> Result<(), String> {
143 use std::io::Write;
144 use std::process::{Command, Stdio};
145 if is_wayland() {
146 if let Ok(mut child) = Command::new("wl-copy").stdin(Stdio::piped()).spawn() {
147 if let Some(stdin) = child.stdin.as_mut() {
148 let _ = stdin.write_all(text.as_bytes());
149 }
150 if let Ok(status) = child.wait() {
151 if status.success() {
152 return Ok(());
153 }
154 }
155 }
156 }
157 let mut child = Command::new("xclip")
159 .args(["-selection", "clipboard"])
160 .stdin(Stdio::piped())
161 .spawn()
162 .or_else(|_| {
163 Command::new("xsel")
164 .args(["--clipboard", "--input"])
165 .stdin(Stdio::piped())
166 .spawn()
167 })
168 .map_err(|e| format!("clipboard write failed (install wl-copy, xclip, or xsel): {}", e))?;
169 child
170 .stdin
171 .as_mut()
172 .unwrap()
173 .write_all(text.as_bytes())
174 .map_err(|e| format!("clipboard write failed: {}", e))?;
175 child.wait().map_err(|e| format!("clipboard write failed: {}", e))?;
176 Ok(())
177}