use crate::error::ComputerError;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ClipboardContents {
Empty,
Text(String),
Image {
width: u32,
height: u32,
rgba: Vec<u8>,
},
}
pub(crate) fn clipboard_get() -> Result<ClipboardContents, ComputerError> {
#[cfg(test)]
{
Err(ComputerError::ExecutionFailed(
"clipboard is disabled in tests".into(),
))
}
#[cfg(not(test))]
{
live_get()
}
}
pub(crate) fn clipboard_set_text(text: &str) -> Result<(), ComputerError> {
#[cfg(test)]
{
let _ = text;
Err(ComputerError::ExecutionFailed(
"clipboard is disabled in tests".into(),
))
}
#[cfg(not(test))]
{
let mut clipboard = arboard::Clipboard::new().map_err(map_error)?;
clipboard.set_text(text).map_err(map_error)
}
}
pub(crate) fn clipboard_set_image(
width: u32,
height: u32,
rgba: &[u8],
) -> Result<(), ComputerError> {
#[cfg(test)]
{
let _ = (width, height, rgba);
Err(ComputerError::ExecutionFailed(
"clipboard is disabled in tests".into(),
))
}
#[cfg(not(test))]
{
let mut clipboard = arboard::Clipboard::new().map_err(map_error)?;
let image = arboard::ImageData {
width: usize::try_from(width).unwrap_or(0),
height: usize::try_from(height).unwrap_or(0),
bytes: std::borrow::Cow::Borrowed(rgba),
};
clipboard.set_image(image).map_err(map_error)
}
}
#[cfg(not(test))]
fn live_get() -> Result<ClipboardContents, ComputerError> {
let mut clipboard = arboard::Clipboard::new().map_err(map_error)?;
match clipboard.get_text() {
Ok(text) if !text.is_empty() => return Ok(ClipboardContents::Text(text)),
Ok(_) | Err(arboard::Error::ContentNotAvailable) => {}
Err(error) => return Err(map_error(error)),
}
match clipboard.get_image() {
Ok(image) => {
let width = u32::try_from(image.width).map_err(|_| {
ComputerError::ExecutionFailed("clipboard image is too wide".into())
})?;
let height = u32::try_from(image.height).map_err(|_| {
ComputerError::ExecutionFailed("clipboard image is too tall".into())
})?;
Ok(ClipboardContents::Image {
width,
height,
rgba: image.bytes.into_owned(),
})
}
Err(arboard::Error::ContentNotAvailable) => Ok(ClipboardContents::Empty),
Err(error) => Err(map_error(error)),
}
}
#[cfg(not(test))]
fn map_error(error: arboard::Error) -> ComputerError {
let text = error.to_string();
#[cfg(target_os = "macos")]
{
let lower = text.to_ascii_lowercase();
if lower.contains("pasteboard")
&& (lower.contains("permission")
|| lower.contains("denied")
|| lower.contains("not allowed"))
{
return ComputerError::PermissionRequired(text);
}
}
ComputerError::ExecutionFailed(format!("clipboard failed: {text}"))
}