Add support for xsel clipboard as well, dynamically select at runtime

Fixes timvisee/ffsend#76
This commit is contained in:
timvisee 2019-03-25 18:55:11 +01:00
parent c2dfd3e174
commit 982027f33c
No known key found for this signature in database
GPG key ID: B8DB720BC383E172
5 changed files with 158 additions and 19 deletions

10
Cargo.lock generated
View file

@ -557,6 +557,7 @@ dependencies = [
"pbr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"prettytable-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"qr2term 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"quale 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rpassword 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1246,6 +1247,14 @@ dependencies = [
"checked_int_cast 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "quale"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "quote"
version = "0.5.2"
@ -2280,6 +2289,7 @@ dependencies = [
"checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915"
"checksum qr2term 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "358175087ab058962f8f4457864ece0db9685ca5a8938385b5c9e279aa781349"
"checksum qrcode 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3553d8614cd59cede2a00db69bc96ea72dfdd92c041600f06b100f6f2699b26"
"checksum quale 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c46811c513cc64cceb23adc27892b3b3470c1790de117610221b97071de8bc8c"
"checksum quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9949cfe66888ffe1d53e6ec9d9f3b70714083854be20fd5e271b232a017401e8"
"checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1"
"checksum rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9"

View file

@ -50,7 +50,17 @@ name = "ffsend"
path = "src/main.rs"
[features]
default = ["archive", "clipboard", "history", "infer-command", "qrcode", "send2", "send3", "urlshorten"]
default = [
"archive",
"clipboard",
"history",
"infer-command",
"qrcode",
"quale",
"send2",
"send3",
"urlshorten"
]
# Compile with file archiving support
archive = ["tar"]
@ -92,14 +102,15 @@ openssl-probe = "0.1"
pbr = "1"
prettytable-rs = "0.8"
qr2term = { version = "0.1", optional = true }
quale = { version = "1.0", optional = true }
rpassword = "3.0"
serde = "1.0"
serde_derive = "1.0"
tar = { version = "0.4", optional = true }
tempfile = "3"
toml = "0.5"
version-compare = "0.0.6"
urlshortener = { version = "0.10", default-features = false, optional = true }
version-compare = "0.0.6"
[target.'cfg(not(target_os = "linux"))'.dependencies]
clipboard = { version = "0.5", optional = true }

View file

@ -490,6 +490,13 @@ empty.
| `FFSEND_QUIET` | `--quiet` | Log quiet information |
| `FFSEND_VERBOSE` | `--verbose` | Log verbose information |
Some environment variables may be set at compile time to tweak some defaults.
| Variable | Description |
| :----------- | :------------------------------------- |
| `XCLIP_PATH` | Set fixed `xclip` binary path on Linux |
| `XSEL_PATH` | Set fixed `xsel` binary path on Linux |
At this time, no configuration or _dotfile_ file support is available.
This will be something added in a later release.

View file

@ -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(all(feature = "clipboard", target_os = "linux"))]
use crate::util::ClipboardType;
use crate::util::{api_version_list, features_list, format_bool, format_duration};
/// A file debug action.
@ -100,11 +102,7 @@ impl<'a> Debug<'a> {
#[cfg(all(feature = "clipboard", target_os = "linux"))]
table.add_row(Row::new(vec![
Cell::new("Clipboard:"),
Cell::new(
&option_env!("XCLIP_PATH")
.map(|path| format!("xclip ({})", path))
.unwrap_or_else(|| "xclip".into()),
),
Cell::new(&format!("{}", ClipboardType::select())),
]));
// Show whether quiet is used

View file

@ -4,12 +4,16 @@ extern crate colored;
extern crate directories;
extern crate fs2;
extern crate open;
#[cfg(all(feature = "clipboard", target_os = "linux"))]
extern crate quale;
use std::borrow::Borrow;
use std::env::{self, current_exe, var_os};
use std::ffi::OsStr;
#[cfg(all(feature = "clipboard", target_os = "linux"))]
use std::fmt;
use std::fmt::{Debug, Display};
#[cfg(all(target_os = "linux", feature = "clipboard"))]
#[cfg(all(feature = "clipboard", target_os = "linux"))]
use std::io::ErrorKind as IoErrorKind;
use std::io::{stderr, stdin, Error as IoError, Write};
use std::path::Path;
@ -284,11 +288,81 @@ pub fn open_path(path: &str) -> Result<ExitStatus, IoError> {
open::that(path)
}
/// Set the clipboard of the user to the given `content` string.
/// 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")]
pub fn set_clipboard(content: String) -> Result<(), ClipboardError> {
#[derive(Clone, Eq, PartialEq)]
pub enum ClipboardType {
/// Native operating system clipboard.
#[cfg(not(target_os = "linux"))]
{
Native,
/// Manage clipboard through `xclip` on Linux.
///
/// May contain a binary path if specified at compile time through the `XCLIP_PATH` variable.
#[cfg(target_os = "linux")]
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(target_os = "linux")]
Xsel(Option<String>),
}
#[cfg(feature = "clipboard")]
impl ClipboardType {
/// Select the clipboard type to use, depending on the runtime system.
pub fn select() -> Self {
#[cfg(not(target_os = "linux"))]
{
ClipboardType::Native
}
#[cfg(target_os = "linux")]
{
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 quale::which("xclip").is_some() {
ClipboardType::Xclip(None)
} else if quale::which("xsel").is_some() {
ClipboardType::Xsel(None)
} else {
// TODO: should we error here instead, as no clipboard binary was found?
ClipboardType::Xclip(None)
}
}
}
/// Set clipboar contents through the selected clipboard type.
pub fn set(&self, content: String) -> Result<(), ClipboardError> {
match self {
#[cfg(not(target_os = "linux"))]
ClipboardType::Native => Self::native_set(content),
#[cfg(target_os = "linux")]
ClipboardType::Xclip(path) => Self::xclip_set(path.clone(), content),
#[cfg(target_os = "linux")]
ClipboardType::Xsel(path) => Self::xsel_set(path.clone(), content),
}
}
/// Set the clipboard through a native interface.
///
/// This is used on non-Linux systems.
#[cfg(not(target_os = "linux"))]
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())
@ -296,14 +370,27 @@ pub fn set_clipboard(content: String) -> Result<(), ClipboardError> {
}
#[cfg(target_os = "linux")]
{
// Open an xclip process
let mut process = match Command::new(option_env!("XCLIP_PATH").unwrap_or("xclip"))
.arg("-sel")
.arg("clip")
.stdin(Stdio::piped())
.spawn()
{
fn xclip_set(path: Option<String>, content: String) -> Result<(), ClipboardError> {
Self::sys_cmd_set(
Command::new(path.unwrap_or_else(|| "xclip".into()))
.arg("-sel")
.arg("clip"),
content,
)
}
#[cfg(target_os = "linux")]
fn xsel_set(path: Option<String>, content: String) -> Result<(), ClipboardError> {
Self::sys_cmd_set(
Command::new(path.unwrap_or_else(|| "xsel".into())).arg("--clipboard"),
content,
)
}
#[cfg(target_os = "linux")]
fn sys_cmd_set(command: &mut Command, content: String) -> Result<(), ClipboardError> {
// Spawn the command process for setting the clipboard
let mut process = match command.stdin(Stdio::piped()).spawn() {
Ok(process) => process,
Err(err) => {
return Err(match err.kind() {
@ -331,6 +418,32 @@ pub fn set_clipboard(content: String) -> Result<(), ClipboardError> {
}
}
#[cfg(feature = "clipboard")]
impl fmt::Display for ClipboardType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
#[cfg(not(target_os = "linux"))]
ClipboardType::Native => write!(f, "native"),
#[cfg(target_os = "linux")]
ClipboardType::Xclip(path) => match path {
None => write!(f, "xclip"),
Some(path) => write!(f, "xclip ({})", path),
},
#[cfg(target_os = "linux")]
ClipboardType::Xsel(path) => match path {
None => write!(f, "xsel"),
Some(path) => write!(f, "xsel ({})", path),
},
}
}
}
/// 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)
}
#[cfg(feature = "clipboard")]
#[derive(Debug, Fail)]
pub enum ClipboardError {