#[cfg(target_os = "linux")]
pub const CLIPBOARD_DAEMON_ID: &str = "__ferrishot_clipboard_daemon";
use std::{fs::File, io::Write};
#[expect(dead_code, reason = "will be used later")]
pub fn set_text(text: &str) -> Result<(), Box<dyn std::error::Error>> {
#[cfg(target_os = "linux")]
{
use std::process;
process::Command::new(std::env::current_exe()?)
.arg(CLIPBOARD_DAEMON_ID)
.arg("text")
.arg(text)
.stdin(process::Stdio::null())
.stdout(process::Stdio::null())
.stderr(process::Stdio::null())
.current_dir("/")
.spawn()?;
}
#[cfg(not(target_os = "linux"))]
{
arboard::Clipboard::new()?.set_text(text)?;
}
Ok(())
}
#[cfg_attr(
target_os = "linux",
expect(
clippy::needless_pass_by_value,
reason = "on non-linux it is passed by value"
)
)]
pub fn set_image(
image_data: arboard::ImageData,
) -> Result<std::path::PathBuf, Box<dyn std::error::Error>> {
let clipboard_buffer_path = tempfile::Builder::new()
.keep(true)
.tempfile()
.expect("failed to create a temporary file, for storage of image data");
let mut clipboard_buffer_file = File::create(&clipboard_buffer_path)?;
clipboard_buffer_file.write_all(&image_data.bytes)?;
#[cfg(target_os = "linux")]
{
use std::process;
process::Command::new(std::env::current_exe()?)
.arg(CLIPBOARD_DAEMON_ID)
.arg("image")
.arg(image_data.width.to_string())
.arg(image_data.height.to_string())
.arg(clipboard_buffer_path.path())
.stdin(process::Stdio::null())
.stdout(process::Stdio::null())
.stderr(process::Stdio::null())
.current_dir("/")
.spawn()?;
}
#[cfg(not(target_os = "linux"))]
{
arboard::Clipboard::new()?.set_image(image_data)?;
}
Ok(clipboard_buffer_path.path().to_path_buf())
}
#[cfg(target_os = "linux")]
pub fn run_clipboard_daemon() -> Result<(), arboard::Error> {
use arboard::SetExtLinux as _;
use std::fs;
log::info!(
"Spawned clipboard daemon with arguments: {:?}",
std::env::args().collect::<Vec<_>>()
);
let mut args = std::env::args().skip(1);
assert_eq!(
args.next().as_deref(),
Some(CLIPBOARD_DAEMON_ID),
"this function must be invoked from a daemon process"
);
match args.next().expect("has copy type").as_str() {
"image" => {
let width = args
.next()
.expect("width")
.parse::<usize>()
.expect("valid image width");
let height = args
.next()
.expect("height")
.parse::<usize>()
.expect("valid image height");
let path = args.next().expect("image path");
let bytes: std::borrow::Cow<[u8]> = fs::read(&path).expect("image contents").into();
assert_eq!(args.next(), None, "unexpected extra args");
assert_eq!(
width * height * 4,
bytes.len(),
"every 4 bytes in `bytes` represents a single RGBA pixel"
);
arboard::Clipboard::new()?
.set()
.wait()
.image(arboard::ImageData {
width,
height,
bytes,
})?;
fs::remove_file(path).expect("failed to remove file");
}
"text" => {
let text = args.next().expect("text");
assert_eq!(args.next(), None, "unexpected extra args");
arboard::Clipboard::new()?.set().wait().text(text)?;
}
_ => panic!("invalid copy type, expected `image` or `text`"),
}
Ok(())
}