pocket_cli/utils/
clipboard.rs

1use anyhow::{Result, anyhow};
2use std::process::{Command, Stdio};
3
4/// Read content from the system clipboard
5/// 
6/// Supports macOS (pbpaste), Windows (PowerShell), and Linux (xclip/wl-paste)
7pub fn read_clipboard() -> Result<String> {
8    #[cfg(target_os = "macos")]
9    {
10        let output = Command::new("pbpaste")
11            .output()
12            .map_err(|_| anyhow!("Failed to access clipboard. Make sure 'pbpaste' is available."))?;
13        
14        if !output.status.success() {
15            return Err(anyhow!("Failed to read from clipboard"));
16        }
17        
18        Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
19    }
20    
21    #[cfg(target_os = "windows")]
22    {
23        let output = Command::new("powershell.exe")
24            .args(["-command", "Get-Clipboard"])
25            .output()
26            .map_err(|_| anyhow!("Failed to access clipboard. Make sure PowerShell is available."))?;
27        
28        if !output.status.success() {
29            return Err(anyhow!("Failed to read from clipboard"));
30        }
31        
32        Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
33    }
34    
35    #[cfg(all(unix, not(target_os = "macos")))]
36    {
37        // Try XClip first (X11)
38        let xclip_result = Command::new("xclip")
39            .args(["-selection", "clipboard", "-o"])
40            .output();
41        
42        // If XClip works, use it
43        if let Ok(output) = xclip_result {
44            if output.status.success() {
45                return Ok(String::from_utf8_lossy(&output.stdout).trim().to_string());
46            }
47        }
48        
49        // Try wl-paste (Wayland)
50        let wl_paste_result = Command::new("wl-paste")
51            .output();
52        
53        // If wl-paste works, use it
54        if let Ok(output) = wl_paste_result {
55            if output.status.success() {
56                return Ok(String::from_utf8_lossy(&output.stdout).trim().to_string());
57            }
58        }
59        
60        // If neither worked, raise an error
61        Err(anyhow!("Failed to access clipboard. Please install xclip (for X11) or wl-paste (for Wayland)."))
62    }
63    
64    // Fallback for unsupported platforms
65    #[cfg(not(any(target_os = "macos", target_os = "windows", all(unix, not(target_os = "macos")))))]
66    {
67        Err(anyhow!("Clipboard functionality is not supported on this platform"))
68    }
69}
70
71/// Write content to the system clipboard
72/// 
73/// Supports macOS (pbcopy), Windows (PowerShell), and Linux (xclip/wl-copy)
74pub fn write_clipboard(content: &str) -> Result<()> {
75    #[cfg(target_os = "macos")]
76    {
77        let mut child = Command::new("pbcopy")
78            .stdin(Stdio::piped())
79            .spawn()
80            .map_err(|_| anyhow!("Failed to access clipboard. Make sure 'pbcopy' is available."))?;
81        
82        if let Some(mut stdin) = child.stdin.take() {
83            use std::io::Write;
84            stdin.write_all(content.as_bytes())?;
85        }
86        
87        let status = child.wait()?;
88        if !status.success() {
89            return Err(anyhow!("Failed to write to clipboard"));
90        }
91        
92        Ok(())
93    }
94    
95    #[cfg(target_os = "windows")]
96    {
97        // PowerShell command to set clipboard
98        let mut child = Command::new("powershell.exe")
99            .args(["-command", "Set-Clipboard -Value $input"])
100            .stdin(Stdio::piped())
101            .spawn()
102            .map_err(|_| anyhow!("Failed to access clipboard. Make sure PowerShell is available."))?;
103        
104        if let Some(mut stdin) = child.stdin.take() {
105            use std::io::Write;
106            stdin.write_all(content.as_bytes())?;
107        }
108        
109        let status = child.wait()?;
110        if !status.success() {
111            return Err(anyhow!("Failed to write to clipboard"));
112        }
113        
114        Ok(())
115    }
116    
117    #[cfg(all(unix, not(target_os = "macos")))]
118    {
119        // Try XClip first (X11)
120        let mut xclip_child = Command::new("xclip")
121            .args(["-selection", "clipboard"])
122            .stdin(Stdio::piped())
123            .spawn();
124        
125        if let Ok(mut child) = xclip_child {
126            if let Some(mut stdin) = child.stdin.take() {
127                use std::io::Write;
128                stdin.write_all(content.as_bytes())?;
129            }
130            
131            let status = child.wait()?;
132            if status.success() {
133                return Ok(());
134            }
135        }
136        
137        // Try wl-copy (Wayland)
138        let mut wl_copy_child = Command::new("wl-copy")
139            .stdin(Stdio::piped())
140            .spawn();
141        
142        if let Ok(mut child) = wl_copy_child {
143            if let Some(mut stdin) = child.stdin.take() {
144                use std::io::Write;
145                stdin.write_all(content.as_bytes())?;
146            }
147            
148            let status = child.wait()?;
149            if status.success() {
150                return Ok(());
151            }
152        }
153        
154        // If neither worked, raise an error
155        Err(anyhow!("Failed to access clipboard. Please install xclip (for X11) or wl-copy (for Wayland)."))
156    }
157    
158    // Fallback for unsupported platforms
159    #[cfg(not(any(target_os = "macos", target_os = "windows", all(unix, not(target_os = "macos")))))]
160    {
161        Err(anyhow!("Clipboard functionality is not supported on this platform"))
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168    
169    #[test]
170    #[ignore] // Ignore by default as it interacts with system clipboard
171    fn test_clipboard_write_read() {
172        let test_content = "Test clipboard content";
173        
174        // Write to clipboard
175        write_clipboard(test_content).expect("Failed to write to clipboard");
176        
177        // Read from clipboard
178        let read_content = read_clipboard().expect("Failed to read from clipboard");
179        
180        assert_eq!(read_content, test_content);
181    }
182}