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 copypasta::x11_clipboard::X11ClipboardContext;
use which::which;
use crate::combined::CombinedClipboardContext;
use crate::display::DisplayServer;
use crate::prelude::*;
pub type ClipboardContext = X11BinClipboardContext;
pub struct X11BinClipboardContext(ClipboardType);
impl X11BinClipboardContext {
pub fn new() -> crate::ClipResult<Self> {
Ok(Self(ClipboardType::select()))
}
pub fn new_with_x11() -> crate::ClipResult<CombinedClipboardContext<X11ClipboardContext, Self>>
{
Self::new()?.with_x11()
}
pub fn with_x11(
self,
) -> crate::ClipResult<CombinedClipboardContext<X11ClipboardContext, Self>> {
Ok(CombinedClipboardContext(X11ClipboardContext::new()?, self))
}
}
impl ClipboardProvider for X11BinClipboardContext {
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 X11BinClipboardContext {
fn display_server(&self) -> Option<DisplayServer> {
Some(DisplayServer::X11)
}
fn has_bin_lifetime(&self) -> bool {
false
}
}
enum ClipboardType {
Xclip(Option<String>),
Xsel(Option<String>),
}
impl ClipboardType {
pub fn select() -> Self {
if let Some(path) = option_env!("XCLIP_PATH") {
ClipboardType::Xclip(Some(path.to_owned()))
} else if let Some(path) = option_env!("XSEL_PATH") {
ClipboardType::Xsel(Some(path.to_owned()))
} else if which("xclip").is_ok() {
ClipboardType::Xclip(None)
} else if which("xsel").is_ok() {
ClipboardType::Xsel(None)
} else {
ClipboardType::Xclip(None)
}
}
pub fn get(&self) -> Result<String, Error> {
match self {
ClipboardType::Xclip(path) => sys_cmd_get(
"xclip",
Command::new(path.as_deref().unwrap_or("xclip"))
.arg("-sel")
.arg("clip")
.arg("-out"),
),
ClipboardType::Xsel(path) => sys_cmd_get(
"xsel",
Command::new(path.as_deref().unwrap_or("xsel"))
.arg("--clipboard")
.arg("--output"),
),
}
}
pub fn set(&self, contents: &str) -> Result<(), Error> {
match self {
ClipboardType::Xclip(path) => sys_cmd_set(
"xclip",
Command::new(path.as_deref().unwrap_or("xclip"))
.arg("-sel")
.arg("clip"),
contents,
),
ClipboardType::Xsel(path) => sys_cmd_set(
"xsel",
Command::new(path.as_deref().unwrap_or("xsel")).arg("--clipboard"),
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 xclip or xsel 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,
}
}
}