1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
//! Invokes [`xclip`][xclip]/[`xsel`][xsel] to access clipboard. //! //! This provider ensures the clipboard contents you set remain available even after your //! application exists, unlike [`X11ClipboardContext`][X11ClipboardContext]. //! //! When getting or setting the clipboard, the `xclip` or `xsel` binary is invoked to manage the //! contents. When setting the clipboard contents, these binaries internally fork and stay alive //! until the clipboard content changes. //! //! The `xclip` or `xsel` must be in `PATH`. Alternatively the paths of either may be set at //! compile time using the `XCLIP_PATH` and `XSEL_PATH` environment variables. If set, the //! clipboard context will automatically use those. //! //! What binary is used is deterimined at runtime on context creation based on the compile time //! variables and the runtime environment. //! //! Use the provided `ClipboardContext` type alias to use this clipboard context on supported //! platforms, but fall back to the standard clipboard on others. //! //! ## Benefits //! //! - Keeps contents in clipboard even after your application exists. //! //! ## Drawbacks //! //! - Requires [`xclip`][xclip] or [`xsel`][xsel] to be available. //! - Less performant than alternatives due to binary invocation. //! - Set contents may not be immediately available, because they are set in an external binary. //! - May have undefined behaviour if `xclip` or `xsel` are modified. //! //! # Examples //! //! ```rust,no_run //! use copypasta_ext::prelude::*; //! use copypasta_ext::x11_bin::X11BinClipboardContext; //! //! let mut ctx = X11BinClipboardContext::new().unwrap(); //! println!("{:?}", ctx.get_contents()); //! ctx.set_contents("some string".into()).unwrap(); //! ``` //! //! Use `ClipboardContext` alias for better platform compatability: //! //! ```rust,no_run //! use copypasta_ext::prelude::*; //! use copypasta_ext::x11_bin::ClipboardContext; //! //! let mut ctx = ClipboardContext::new().unwrap(); //! println!("{:?}", ctx.get_contents()); //! ctx.set_contents("some string".into()).unwrap(); //! ``` //! //! Use `new_with_x11` to combine with [`X11ClipboardContext`][X11ClipboardContext] for better performance. //! //! ```rust,no_run //! use copypasta_ext::prelude::*; //! use copypasta_ext::x11_bin::X11BinClipboardContext; //! //! let mut ctx = X11BinClipboardContext::new_with_x11().unwrap(); //! println!("{:?}", ctx.get_contents()); //! ctx.set_contents("some string".into()).unwrap(); //! ``` //! //! [X11ClipboardContext]: https://docs.rs/copypasta/*/copypasta/x11_clipboard/struct.X11ClipboardContext.html //! [x11_clipboard]: https://docs.rs/copypasta/*/copypasta/x11_clipboard/index.html //! [xclip]: https://github.com/astrand/xclip //! [xsel]: http://www.vergenet.net/~conrad/software/xsel/ 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, ClipboardProvider}; use which::which; use crate::combined::CombinedClipboardContext; /// Platform specific context. /// /// Alias for `X11BinClipboardContext` on supported platforms, aliases to standard /// `ClipboardContext` provided by `rust-clipboard` on other platforms. pub type ClipboardContext = X11BinClipboardContext; /// Invokes [`xclip`][xclip]/[`xsel`][xsel] to access clipboard. /// /// See module documentation for more information. /// /// [xclip]: https://github.com/astrand/xclip /// [xsel]: http://www.vergenet.net/~conrad/software/xsel/ pub struct X11BinClipboardContext(ClipboardType); impl X11BinClipboardContext { pub fn new() -> crate::ClipResult<Self> { Ok(Self(ClipboardType::select())) } /// Construct combined with [`X11ClipboardContext`][X11ClipboardContext]. /// /// This clipboard context invokes a binary for getting the clipboard contents. This may /// be considered inefficient and has other drawbacks as noted in the struct documentation. /// This function also constructs a `X11ClipboardContext` for getting clipboard contents and /// combines the two to get the best of both worlds. /// /// [X11ClipboardContext]: https://docs.rs/copypasta/*/copypasta/x11_clipboard/struct.X11ClipboardContext.html pub fn new_with_x11() -> crate::ClipResult<CombinedClipboardContext<X11ClipboardContext, Self>> { Self::new()?.with_x11() } /// Combine this context with [`X11ClipboardContext`][X11ClipboardContext]. /// /// This clipboard context invokes a binary for getting the clipboard contents. This may /// be considered inefficient and has other drawbacks as noted in the struct documentation. /// This function constructs a `X11ClipboardContext` for getting clipboard contents and /// combines the two to get the best of both worlds. /// /// [X11ClipboardContext]: https://docs.rs/copypasta/*/copypasta/x11_clipboard/struct.X11ClipboardContext.html 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)?) } } /// Available clipboard management binaries. /// /// Invoke `ClipboardType::select()` to select the best variant to use determined at runtime. enum ClipboardType { /// Use `xclip`. /// /// May contain a binary path if specified at compile time through the `XCLIP_PATH` variable. Xclip(Option<String>), /// Use `xsel`. /// /// May contain a binary path if specified at compile time through the `XSEL_PATH` variable. Xsel(Option<String>), } impl ClipboardType { /// Select the clipboard type to use. 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 { // TODO: should we error here instead, as no clipboard binary was found? ClipboardType::Xclip(None) } } /// Get clipboard contents through the selected clipboard type. pub fn get(&self) -> Result<String, Error> { match self { ClipboardType::Xclip(path) => sys_cmd_get( "xclip", Command::new(path.as_deref().unwrap_or_else(|| "xclip")) .arg("-sel") .arg("clip") .arg("-out"), ), ClipboardType::Xsel(path) => sys_cmd_get( "xsel", Command::new(path.as_deref().unwrap_or_else(|| "xsel")) .arg("--clipboard") .arg("--output"), ), } } /// Set clipboard contents through the selected clipboard type. pub fn set(&self, contents: &str) -> Result<(), Error> { match self { ClipboardType::Xclip(path) => sys_cmd_set( "xclip", Command::new(path.as_deref().unwrap_or_else(|| "xclip")) .arg("-sel") .arg("clip"), contents, ), ClipboardType::Xsel(path) => sys_cmd_set( "xsel", Command::new(path.as_deref().unwrap_or_else(|| "xsel")).arg("--clipboard"), contents, ), } } } /// Get clipboard contents using a system command. fn sys_cmd_get(bin: &'static str, command: &mut Command) -> Result<String, Error> { // Spawn the command process for getting the clipboard let output = match command.output() { Ok(output) => output, Err(err) => { return Err(match err.kind() { IoErrorKind::NotFound => Error::NoBinary, _ => Error::BinaryIo(bin, err), }); } }; // Check process status code if !output.status.success() { return Err(Error::BinaryStatus(bin, output.status.code().unwrap_or(0))); } // Get and parse output String::from_utf8(output.stdout).map_err(Error::NoUtf8) } /// Set clipboard contents using a system command. fn sys_cmd_set(bin: &'static str, command: &mut Command, contents: &str) -> Result<(), Error> { // Spawn the command process for setting the clipboard 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), }); } }; // Write the contents to the xclip process process .stdin .as_mut() .unwrap() .write_all(contents.as_bytes()) .map_err(|err| Error::BinaryIo(bin, err))?; // Wait for process to exit 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(()) } /// Represents X11 binary related error. #[derive(Debug)] pub enum Error { /// The `xclip` or `xsel` binary could not be found on the system, required for clipboard support. NoBinary, /// An error occurred while using `xclip` or `xsel` to manage the clipboard contents. /// This problem probably occurred when starting, or while piping the clipboard contents /// from/to the process. BinaryIo(&'static str, IoError), /// `xclip` or `xsel` unexpectetly exited with a non-successful status code. BinaryStatus(&'static str, i32), /// The clipboard contents could not be parsed as valid UTF-8. 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, } } }