Explorar o código

Revert "Remove our complex clipboard logic, use clipboard-ext crate instead"

This reverts commit 8bfe173a181afad77b8b93106d20ac6a4799bea3.
timvisee %!s(int64=5) %!d(string=hai) anos
pai
achega
5f21b87937
Modificáronse 7 ficheiros con 283 adicións e 28 borrados
  1. 2 13
      Cargo.lock
  2. 15 2
      Cargo.toml
  3. 2 2
      README.md
  4. 37 0
      build.rs
  5. 9 0
      src/action/debug.rs
  6. 3 4
      src/action/upload.rs
  7. 215 7
      src/util.rs

+ 2 - 13
Cargo.lock

@@ -253,17 +253,6 @@ dependencies = [
  "x11-clipboard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
-[[package]]
-name = "clipboard-ext"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "clipboard 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
- "which 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "x11-clipboard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
 [[package]]
 name = "clipboard-win"
 version = "2.2.0"
@@ -600,7 +589,7 @@ dependencies = [
  "chbs 0.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
  "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "clipboard-ext 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "clipboard 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "colored 1.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "derive_builder 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "directories 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -623,6 +612,7 @@ dependencies = [
  "toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "urlshortener 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "version-compare 0.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "which 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -2734,7 +2724,6 @@ dependencies = [
 "checksum chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01"
 "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
 "checksum clipboard 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "25a904646c0340239dcf7c51677b33928bf24fdf424b79a57909c0109075b2e7"
-"checksum clipboard-ext 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2513f2e8ada5da8d9a368f764092cd35eeb4e0c2d56fc43948c2962645324b22"
 "checksum clipboard-win 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e3a093d6fed558e5fe24c3dfc85a68bb68f1c824f440d3ba5aca189e2998786b"
 "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
 "checksum colored 1.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8815e2ab78f3a59928fc32e141fbeece88320a240e43f47b2fd64ea3a88a5b3d"

+ 15 - 2
Cargo.toml

@@ -28,6 +28,7 @@ exclude = [
     "/SECURITY.md",
 ]
 edition = "2018"
+build = "build.rs"
 
 [package.metadata.deb]
 section = "utility"
@@ -71,7 +72,7 @@ default = [
 archive = ["tar"]
 
 # Support for putting share URLs in clipboard
-clipboard = ["clipboard-ext"]
+clipboard = ["clip", "which"]
 
 # Compile with file history support
 history = []
@@ -94,11 +95,16 @@ infer-command = []
 # Compile without colored output support
 no-color = ["colored/no-color"]
 
+# Automatic using build.rs: use xclip/xsel binary method for clipboard support
+clipboard-bin = ["clipboard"]
+
+# Automatic using build.rs: use native clipboard crate for clipboard support
+clipboard-crate = ["clipboard"]
+
 [dependencies]
 chbs = "0.0.9"
 chrono = "0.4"
 clap = "2.33"
-clipboard-ext = { version = "0.1", optional = true }
 colored = "1.9"
 derive_builder = "0.9"
 directories = "2.0"
@@ -121,3 +127,10 @@ tempfile = "3"
 toml = "0.5"
 urlshortener = { version = "2", optional = true }
 version-compare = "0.0.10"
+
+[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
+which = { version = "3.1", optional = true }
+
+[target.'cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd", target_os = "netbsd")))'.dependencies]
+# Aliased to clip to prevent name collision with clipboard feature
+clip = { version = "0.5", optional = true, package = "clipboard" }

+ 2 - 2
README.md

@@ -574,8 +574,8 @@ Some environment variables may be set at compile time to tweak some defaults.
 
 | Variable     | Description                                                                |
 | :----------- | :------------------------------------------------------------------------- |
-| `XCLIP_PATH` | Set fixed `xclip` binary path when using `clipboard` (Linux, *BSD) |
-| `XSEL_PATH`  | Set fixed `xsel` binary path when using `clipboard` (Linux, *BSD)  |
+| `XCLIP_PATH` | Set fixed `xclip` binary path when using `clipboard-bin` (Linux, *BSD) |
+| `XSEL_PATH`  | Set fixed `xsel` binary path when using `clipboard-bin` (Linux, *BSD)  |
 
 At this time, no configuration or _dotfile_ file support is available.
 This will be something added in a later release.

+ 37 - 0
build.rs

@@ -0,0 +1,37 @@
+fn main() {
+    #[cfg(all(
+        feature = "clipboard",
+        any(
+            target_os = "linux",
+            target_os = "freebsd",
+            target_os = "dragonfly",
+            target_os = "openbsd",
+            target_os = "netbsd",
+        )
+    ))]
+    {
+        // Select clipboard binary method
+        #[cfg(not(feature = "clipboard-crate"))]
+        println!("cargo:rustc-cfg=feature=\"clipboard-bin\"");
+
+        // xclip and xsel paths are inserted at compile time
+        println!("cargo:rerun-if-env-changed=XCLIP_PATH");
+        println!("cargo:rerun-if-env-changed=XSEL_PATH");
+    }
+
+    #[cfg(all(
+        feature = "clipboard",
+        not(any(
+            target_os = "linux",
+            target_os = "freebsd",
+            target_os = "dragonfly",
+            target_os = "openbsd",
+            target_os = "netbsd",
+        ))
+    ))]
+    {
+        // Select clipboard crate method
+        #[cfg(not(feature = "clipboard-bin"))]
+        println!("cargo:rustc-cfg=feature=\"clipboard-crate\"");
+    }
+}

+ 9 - 0
src/action/debug.rs

@@ -6,6 +6,8 @@ use prettytable::{format::FormatBuilder, Cell, Row, Table};
 use crate::client::to_duration;
 use crate::cmd::matcher::{debug::DebugMatcher, main::MainMatcher, Matcher};
 use crate::error::ActionError;
+#[cfg(feature = "clipboard-bin")]
+use crate::util::ClipboardType;
 use crate::util::{api_version_list, features_list, format_bool, format_duration};
 
 /// A file debug action.
@@ -96,6 +98,13 @@ impl<'a> Debug<'a> {
             Cell::new(&api_version_list().join(", ")),
         ]));
 
+        // Clipboard information
+        #[cfg(feature = "clipboard-bin")]
+        table.add_row(Row::new(vec![
+            Cell::new("Clipboard:"),
+            Cell::new(&format!("{}", ClipboardType::select())),
+        ]));
+
         // Show whether quiet is used
         table.add_row(Row::new(vec![
             Cell::new("Quiet:"),

+ 3 - 4
src/action/upload.rs

@@ -444,10 +444,9 @@ impl<'a> Upload<'a> {
         {
             if let Some(copy_mode) = matcher_upload.copy() {
                 if let Err(err) = set_clipboard(copy_mode.build(url.as_str())) {
-                    print_error_msg(format!(
-                        "failed to copy the share link to the clipboard, ignoring: {}",
-                        err,
-                    ));
+                    print_error(
+                        err.context("failed to copy the share link to the clipboard, ignoring"),
+                    );
                 }
             }
         }

+ 215 - 7
src/util.rs

@@ -1,28 +1,38 @@
+#[cfg(feature = "clipboard-crate")]
+extern crate clip;
 extern crate colored;
 extern crate directories;
 extern crate fs2;
 extern crate open;
 extern crate regex;
+#[cfg(feature = "clipboard-bin")]
+extern crate which;
 
 use std::borrow::Borrow;
 use std::env::{self, current_exe, var_os};
-#[cfg(feature = "clipboard")]
-use std::error::Error as StdError;
 use std::ffi::OsStr;
+#[cfg(feature = "clipboard")]
+use std::fmt;
 use std::fmt::{Debug, Display};
+#[cfg(feature = "clipboard-bin")]
+use std::io::ErrorKind as IoErrorKind;
 use std::io::{stderr, stdin, Error as IoError, Write};
 use std::path::Path;
 use std::path::PathBuf;
 use std::process::{exit, ExitStatus};
+#[cfg(feature = "clipboard-bin")]
+use std::process::{Command, Stdio};
 
+#[cfg(feature = "clipboard-crate")]
+use self::clip::{ClipboardContext, ClipboardProvider};
 use self::colored::*;
 #[cfg(feature = "history")]
 use self::directories::ProjectDirs;
 use self::fs2::available_space;
 use chrono::Duration;
-#[cfg(feature = "clipboard")]
-use clipboard_ext::{prelude::*, x11_bin::ClipboardContext};
 use failure::{err_msg, Fail};
+#[cfg(feature = "clipboard-crate")]
+use failure::{Compat, Error};
 use ffsend_api::{
     api::request::{ensure_success, ResponseError},
     client::Client,
@@ -31,6 +41,8 @@ use ffsend_api::{
 };
 use regex::Regex;
 use rpassword::prompt_password_stderr;
+#[cfg(feature = "clipboard-bin")]
+use which::which;
 
 use crate::cmd::matcher::MainMatcher;
 
@@ -291,10 +303,202 @@ pub fn open_path(path: &str) -> Result<ExitStatus, IoError> {
     open::that(path)
 }
 
-/// Set the clipboard of the user to the given `contents` string.
+/// Set the clipboard of the user to the given `content` string.
+#[cfg(feature = "clipboard")]
+pub fn set_clipboard(content: String) -> Result<(), ClipboardError> {
+    ClipboardType::select().set(content)
+}
+
+/// Clipboard management enum.
+///
+/// Defines which method of setting the clipboard is used.
+/// Invoke `ClipboardType::select()` to select the best variant to use determined at runtime.
+///
+/// Usually, the `Native` variant is used. However, on Linux system a different variant will be
+/// selected which will call a system binary to set the clipboard. This must be done because the
+/// native clipboard interface only has a lifetime of the application. This means that the
+/// clipboard is instantly cleared as soon as this application quits, which is always immediately.
+/// This limitation is due to security reasons as defined by X11. The alternative binaries we set
+/// the clipboard with spawn a daemon in the background to keep the clipboad alive until it's
+/// flushed.
+#[cfg(feature = "clipboard")]
+#[derive(Clone, Eq, PartialEq)]
+pub enum ClipboardType {
+    /// Native operating system clipboard.
+    #[cfg(feature = "clipboard-crate")]
+    Native,
+
+    /// Manage clipboard through `xclip` on Linux.
+    ///
+    /// May contain a binary path if specified at compile time through the `XCLIP_PATH` variable.
+    #[cfg(feature = "clipboard-bin")]
+    Xclip(Option<String>),
+
+    /// Manage clipboard through `xsel` on Linux.
+    ///
+    /// May contain a binary path if specified at compile time through the `XSEL_PATH` variable.
+    #[cfg(feature = "clipboard-bin")]
+    Xsel(Option<String>),
+}
+
+#[cfg(feature = "clipboard")]
+impl ClipboardType {
+    /// Select the clipboard type to use, depending on the runtime system.
+    pub fn select() -> Self {
+        #[cfg(feature = "clipboard-crate")]
+        {
+            ClipboardType::Native
+        }
+
+        #[cfg(feature = "clipboard-bin")]
+        {
+            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)
+            }
+        }
+    }
+
+    /// Set clipboard contents through the selected clipboard type.
+    pub fn set(&self, content: String) -> Result<(), ClipboardError> {
+        match self {
+            #[cfg(feature = "clipboard-crate")]
+            ClipboardType::Native => Self::native_set(content),
+            #[cfg(feature = "clipboard-bin")]
+            ClipboardType::Xclip(path) => Self::xclip_set(path.clone(), &content),
+            #[cfg(feature = "clipboard-bin")]
+            ClipboardType::Xsel(path) => Self::xsel_set(path.clone(), &content),
+        }
+    }
+
+    /// Set the clipboard through a native interface.
+    ///
+    /// This is used on non-Linux systems.
+    #[cfg(feature = "clipboard-crate")]
+    fn native_set(content: String) -> Result<(), ClipboardError> {
+        ClipboardProvider::new()
+            .and_then(|mut context: ClipboardContext| context.set_contents(content))
+            .map_err(|err| format_err!("{}", err).compat())
+            .map_err(ClipboardError::Native)
+    }
+
+    #[cfg(feature = "clipboard-bin")]
+    fn xclip_set(path: Option<String>, content: &str) -> Result<(), ClipboardError> {
+        Self::sys_cmd_set(
+            "xclip",
+            Command::new(path.unwrap_or_else(|| "xclip".into()))
+                .arg("-sel")
+                .arg("clip"),
+            content,
+        )
+    }
+
+    #[cfg(feature = "clipboard-bin")]
+    fn xsel_set(path: Option<String>, content: &str) -> Result<(), ClipboardError> {
+        Self::sys_cmd_set(
+            "xsel",
+            Command::new(path.unwrap_or_else(|| "xsel".into())).arg("--clipboard"),
+            content,
+        )
+    }
+
+    #[cfg(feature = "clipboard-bin")]
+    fn sys_cmd_set(
+        bin: &'static str,
+        command: &mut Command,
+        content: &str,
+    ) -> Result<(), ClipboardError> {
+        // 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 => ClipboardError::NoBinary,
+                    _ => ClipboardError::BinaryIo(bin, err),
+                });
+            }
+        };
+
+        // Write the contents to the xclip process
+        process
+            .stdin
+            .as_mut()
+            .unwrap()
+            .write_all(content.as_bytes())
+            .map_err(|err| ClipboardError::BinaryIo(bin, err))?;
+
+        // Wait for xclip to exit
+        let status = process
+            .wait()
+            .map_err(|err| ClipboardError::BinaryIo(bin, err))?;
+        if !status.success() {
+            return Err(ClipboardError::BinaryStatus(
+                bin,
+                status.code().unwrap_or(0),
+            ));
+        }
+
+        Ok(())
+    }
+}
+
 #[cfg(feature = "clipboard")]
-pub fn set_clipboard(contents: String) -> Result<(), Box<dyn StdError>> {
-    Ok(ClipboardContext::new()?.set_contents(contents)?)
+impl fmt::Display for ClipboardType {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            #[cfg(feature = "clipboard-crate")]
+            ClipboardType::Native => write!(f, "native"),
+            #[cfg(feature = "clipboard-bin")]
+            ClipboardType::Xclip(path) => match path {
+                None => write!(f, "xclip"),
+                Some(path) => write!(f, "xclip ({})", path),
+            },
+            #[cfg(feature = "clipboard-bin")]
+            ClipboardType::Xsel(path) => match path {
+                None => write!(f, "xsel"),
+                Some(path) => write!(f, "xsel ({})", path),
+            },
+        }
+    }
+}
+
+#[cfg(feature = "clipboard")]
+#[derive(Debug, Fail)]
+pub enum ClipboardError {
+    /// A generic error occurred while setting the clipboard contents.
+    ///
+    /// This is for non-Linux systems, using a native clipboard interface.
+    #[cfg(feature = "clipboard-crate")]
+    #[fail(display = "failed to access clipboard")]
+    Native(#[cause] Compat<Error>),
+
+    /// The `xclip` or `xsel` binary could not be found on the system, required for clipboard support.
+    #[cfg(feature = "clipboard-bin")]
+    #[fail(display = "failed to access clipboard, xclip or xsel is not installed")]
+    NoBinary,
+
+    /// An error occurred while using `xclip` or `xsel` to set the clipboard contents.
+    /// This problem probably occurred when starting, or while piping the clipboard contents to
+    /// the process.
+    #[cfg(feature = "clipboard-bin")]
+    #[fail(display = "failed to access clipboard using {}", _0)]
+    BinaryIo(&'static str, #[cause] IoError),
+
+    /// `xclip` or `xsel` unexpectetly exited with a non-successful status code.
+    #[cfg(feature = "clipboard-bin")]
+    #[fail(
+        display = "failed to use clipboard, {} exited with status code {}",
+        _0, _1
+    )]
+    BinaryStatus(&'static str, i32),
 }
 
 /// Check for an emtpy password in the given `password`.
@@ -845,6 +1049,10 @@ pub fn features_list() -> Vec<&'static str> {
     features.push("archive");
     #[cfg(feature = "clipboard")]
     features.push("clipboard");
+    #[cfg(feature = "clipboard-bin")]
+    features.push("clipboard-bin");
+    #[cfg(feature = "clipboard-crate")]
+    features.push("clipboard-crate");
     #[cfg(feature = "history")]
     features.push("history");
     #[cfg(feature = "qrcode")]