use std::error::Error as StdError;
use std::fmt;
use std::io::{Error as IoError, ErrorKind as IoErrorKind, Write};
use std::process::{Command, Stdio};
use std::string::FromUtf8Error;
use crate::display::DisplayServer;
use crate::prelude::*;
pub type ClipboardContext = WaylandBinClipboardContext;
pub struct WaylandBinClipboardContext(ClipboardType);
impl WaylandBinClipboardContext {
pub fn new() -> crate::ClipResult<Self> {
Ok(Self(ClipboardType::select()))
}
}
impl ClipboardProvider for WaylandBinClipboardContext {
fn get_contents(&mut self) -> crate::ClipResult<String> {
Ok(self.0.get()?)
}
fn set_contents(&mut self, contents: String) -> crate::ClipResult<()> {
Ok(self.0.set(&contents)?)
}
}
impl ClipboardProviderExt for WaylandBinClipboardContext {
fn display_server(&self) -> Option<DisplayServer> {
Some(DisplayServer::Wayland)
}
fn has_bin_lifetime(&self) -> bool {
false
}
}
enum ClipboardType {
WlClipboard(Option<String>, Option<String>),
}
impl ClipboardType {
pub fn select() -> Self {
if option_env!("WL_COPY_PATH").is_some() || option_env!("WL_PASTE_PATH").is_some() {
ClipboardType::WlClipboard(
option_env!("WL_COPY_PATH")
.filter(|p| !p.trim().is_empty())
.map(|p| p.into()),
option_env!("WL_PASTE_PATH")
.filter(|p| !p.trim().is_empty())
.map(|p| p.into()),
)
} else {
ClipboardType::WlClipboard(None, None)
}
}
pub fn get(&self) -> Result<String, Error> {
match self {
ClipboardType::WlClipboard(_, path) => sys_cmd_get(
"wl-paste",
&mut Command::new(path.as_deref().unwrap_or("wl-paste")),
),
}
}
pub fn set(&self, contents: &str) -> Result<(), Error> {
match self {
ClipboardType::WlClipboard(path, _) => sys_cmd_set(
"wl-copy",
&mut Command::new(path.as_deref().unwrap_or("wl-copy")),
contents,
),
}
}
}
fn sys_cmd_get(bin: &'static str, command: &mut Command) -> Result<String, Error> {
let output = match command.output() {
Ok(output) => output,
Err(err) => {
return Err(match err.kind() {
IoErrorKind::NotFound => Error::NoBinary,
_ => Error::BinaryIo(bin, err),
});
}
};
if !output.status.success() {
return Err(Error::BinaryStatus(bin, output.status.code().unwrap_or(0)));
}
String::from_utf8(output.stdout).map_err(Error::NoUtf8)
}
fn sys_cmd_set(bin: &'static str, command: &mut Command, contents: &str) -> Result<(), Error> {
let mut process = match command.stdin(Stdio::piped()).stdout(Stdio::null()).spawn() {
Ok(process) => process,
Err(err) => {
return Err(match err.kind() {
IoErrorKind::NotFound => Error::NoBinary,
_ => Error::BinaryIo(bin, err),
});
}
};
process
.stdin
.as_mut()
.unwrap()
.write_all(contents.as_bytes())
.map_err(|err| Error::BinaryIo(bin, err))?;
let status = process.wait().map_err(|err| Error::BinaryIo(bin, err))?;
if !status.success() {
return Err(Error::BinaryStatus(bin, status.code().unwrap_or(0)));
}
Ok(())
}
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
NoBinary,
BinaryIo(&'static str, IoError),
BinaryStatus(&'static str, i32),
NoUtf8(FromUtf8Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::NoBinary => write!(
f,
"Could not find wl-copy or wl-paste binary for clipboard support"
),
Error::BinaryIo(cmd, err) => {
write!(f, "Failed to access clipboard using {}: {}", cmd, err)
}
Error::BinaryStatus(cmd, code) => write!(
f,
"Failed to use clipboard, {} exited with status code {}",
cmd, code
),
Error::NoUtf8(err) => write!(
f,
"Failed to parse clipboard contents as valid UTF-8: {}",
err
),
}
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
Error::BinaryIo(_, err) => Some(err),
Error::NoUtf8(err) => Some(err),
_ => None,
}
}
}