//! X11 Clipboard implementation //! //! Note that the x11 implementation is really crap right now - we just depend //! on xclip being on the user's path. If x11 pasting doesn't work, it's //! probably because xclip is unavailable. There's currently no non-GPL x11 //! clipboard library for Rust. Until then, we have this hack. //! //! FIXME: Implement actual X11 clipboard API using the ICCCM reference //! https://tronche.com/gui/x/icccm/ use std::ffi::OsStr; use std::io; use std::process::{Command, Output}; use std::string::FromUtf8Error; use super::{Load, Store}; /// The x11 clipboard pub struct Clipboard; #[derive(Debug)] pub enum Error { Io(io::Error), Xclip(String), Utf8(FromUtf8Error), } impl ::std::error::Error for Error { fn cause(&self) -> Option<&::std::error::Error> { match *self { Error::Io(ref err) => Some(err), Error::Utf8(ref err) => Some(err), _ => None, } } fn description(&self) -> &str { match *self { Error::Io(..) => "Error calling xclip", Error::Xclip(..) => "Error reported by xclip", Error::Utf8(..) => "Clipboard contents not utf8", } } } impl ::std::fmt::Display for Error { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { match *self { Error::Io(ref err) => match err.kind() { io::ErrorKind::NotFound => { write!(f, "Please install `xclip` to enable clipboard support") }, _ => write!(f, "Error calling xclip: {}", err), }, Error::Xclip(ref s) => write!(f, "Error from xclip: {}", s), Error::Utf8(ref err) => write!(f, "Error parsing xclip output: {}", err), } } } impl From for Error { fn from(val: io::Error) -> Error { Error::Io(val) } } impl From for Error { fn from(val: FromUtf8Error) -> Error { Error::Utf8(val) } } impl Load for Clipboard { type Err = Error; fn new() -> Result { Ok(Clipboard) } fn load_primary(&self) -> Result { let output = Command::new("xclip").args(&["-o", "-selection", "clipboard"]).output()?; Clipboard::process_xclip_output(output) } fn load_selection(&self) -> Result { let output = Command::new("xclip").args(&["-o"]).output()?; Clipboard::process_xclip_output(output) } } impl Store for Clipboard { /// Sets the primary clipboard contents #[inline] fn store_primary(&mut self, contents: S) -> Result<(), Self::Err> where S: Into, { self.store(contents, &["-i", "-selection", "clipboard"]) } /// Sets the secondary clipboard contents #[inline] fn store_selection(&mut self, contents: S) -> Result<(), Self::Err> where S: Into, { self.store(contents, &["-i"]) } } impl Clipboard { fn process_xclip_output(output: Output) -> Result { if output.status.success() { String::from_utf8(output.stdout).map_err(::std::convert::From::from) } else { String::from_utf8(output.stderr).map_err(::std::convert::From::from) } } fn store(&mut self, contents: C, args: &[S]) -> Result<(), Error> where C: Into, S: AsRef, { use std::io::Write; use std::process::{Command, Stdio}; let contents = contents.into(); let mut child = Command::new("xclip").args(args).stdin(Stdio::piped()).spawn()?; if let Some(stdin) = child.stdin.as_mut() { stdin.write_all(contents.as_bytes())?; } // Return error if didn't exit cleanly let exit_status = child.wait()?; if exit_status.success() { Ok(()) } else { Err(Error::Xclip("xclip returned non-zero exit code".into())) } } } #[cfg(test)] mod tests { use super::Clipboard; use {Load, Store}; #[test] fn clipboard_works() { let mut clipboard = Clipboard::new().expect("create clipboard"); let arst = "arst"; let oien = "oien"; clipboard.store_primary(arst).expect("store selection"); clipboard.store_selection(oien).expect("store selection"); let selection = clipboard.load_selection().expect("load selection"); let primary = clipboard.load_primary().expect("load selection"); assert_eq!(arst, primary); assert_eq!(oien, selection); } }