From 6ea52cb1bc6419e97d93088809e363e95b469caa Mon Sep 17 00:00:00 2001 From: Henrik Friedrichsen Date: Mon, 4 Mar 2024 11:58:23 +0100 Subject: [PATCH] feat(sharing): Switch to `arboard` crate --- CHANGELOG.md | 4 + Cargo.lock | 226 ++++++++++++++++++++++++++++++++++++------ Cargo.toml | 9 +- src/sharing.rs | 198 +++--------------------------------- src/ui/contextmenu.rs | 2 +- src/ui/cover.rs | 2 +- src/ui/listview.rs | 8 +- 7 files changed, 225 insertions(+), 224 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 192184541..4b265637d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Instructions for installation with winget +### Changed + +- Switch to `arboard` for clipboard access as it is still maintained + ### Fixed - Crash on Android (Termux) due to unknown user runtime directory diff --git a/Cargo.lock b/Cargo.lock index befde9c70..51b54f51a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -167,6 +167,26 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "arboard" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2041f1943049c7978768d84e6d0fd95de98b76d6c4727b09e78ec253d29fa58" +dependencies = [ + "clipboard-win", + "core-graphics", + "image", + "log", + "objc", + "objc-foundation", + "objc_id", + "parking_lot 0.12.1", + "thiserror", + "windows-sys 0.48.0", + "wl-clipboard-rs", + "x11rb", +] + [[package]] name = "async-broadcast" version = "0.5.1" @@ -491,6 +511,12 @@ version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" +[[package]] +name = "bytemuck" +version = "1.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" + [[package]] name = "byteorder" version = "1.5.0" @@ -621,26 +647,19 @@ dependencies = [ ] [[package]] -name = "clipboard" -version = "0.5.0" +name = "clipboard-win" +version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25a904646c0340239dcf7c51677b33928bf24fdf424b79a57909c0109075b2e7" +checksum = "12f9a0700e0127ba15d1d52dd742097f821cd9c65939303a44d970465040a297" dependencies = [ - "clipboard-win", - "objc", - "objc-foundation", - "objc_id", - "x11-clipboard", + "error-code", ] [[package]] -name = "clipboard-win" -version = "2.2.0" +name = "color_quant" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a093d6fed558e5fe24c3dfc85a68bb68f1c824f440d3ba5aca189e2998786b" -dependencies = [ - "winapi", -] +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "colorchoice" @@ -711,6 +730,30 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "core-graphics" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "970a29baf4110c26fedbc7f82107d42c23f7e88e404c4577ed73fe99ff85a212" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + [[package]] name = "coreaudio-rs" version = "0.10.0" @@ -764,6 +807,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-channel" version = "0.5.12" @@ -1167,6 +1219,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "error-code" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b" + [[package]] name = "event-listener" version = "2.5.3" @@ -1241,6 +1299,15 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +[[package]] +name = "fdeflate" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +dependencies = [ + "simd-adler32", +] + [[package]] name = "fern" version = "0.6.2" @@ -1256,6 +1323,16 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1268,7 +1345,28 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "foreign-types-shared", + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", ] [[package]] @@ -1277,6 +1375,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -1413,6 +1517,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", +] + [[package]] name = "getrandom" version = "0.2.12" @@ -1660,6 +1774,20 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-traits", + "png", + "tiff", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -1750,6 +1878,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + [[package]] name = "js-sys" version = "0.3.68" @@ -2103,6 +2237,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", + "simd-adler32", ] [[package]] @@ -2139,9 +2274,9 @@ dependencies = [ name = "ncspot" version = "1.0.0" dependencies = [ + "arboard", "chrono", "clap", - "clipboard", "crossbeam-channel", "cursive", "cursive_buffered_backend", @@ -2173,7 +2308,6 @@ dependencies = [ "toml", "unicode-width", "url", - "wl-clipboard-rs", "zbus 4.1.2", ] @@ -2581,7 +2715,7 @@ checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ "bitflags 2.4.2", "cfg-if", - "foreign-types", + "foreign-types 0.3.2", "libc", "once_cell", "openssl-macros", @@ -2801,6 +2935,19 @@ dependencies = [ "dirs-next 1.0.2", ] +[[package]] +name = "png" +version = "0.17.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "polling" version = "2.8.0" @@ -3501,6 +3648,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "slab" version = "0.4.9" @@ -3742,6 +3895,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + [[package]] name = "time" version = "0.3.34" @@ -4280,6 +4444,12 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + [[package]] name = "winapi" version = "0.3.9" @@ -4529,23 +4699,21 @@ dependencies = [ ] [[package]] -name = "x11-clipboard" -version = "0.3.3" +name = "x11rb" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89bd49c06c9eb5d98e6ba6536cf64ac9f7ee3a009b2f53996d405b3944f6bcea" +checksum = "f8f25ead8c7e4cba123243a6367da5d3990e0d3affa708ea19dce96356bd9f1a" dependencies = [ - "xcb", + "gethostname", + "rustix 0.38.31", + "x11rb-protocol", ] [[package]] -name = "xcb" -version = "0.8.2" +name = "x11rb-protocol" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e917a3f24142e9ff8be2414e36c649d47d6cc2ba81f16201cdef96e533e02de" -dependencies = [ - "libc", - "log", -] +checksum = "e63e71c4b8bd9ffec2c963173a4dc4cbde9ee96961d4fcb4429db9929b606c34" [[package]] name = "xdg-home" diff --git a/Cargo.toml b/Cargo.toml index e0aad2d36..7e100aacc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ codegen-units = 16 [dependencies] chrono = "0.4" clap = "4.5.1" -clipboard = {version = "0.5", optional = true} +arboard = {version = "3.3", optional = true} crossbeam-channel = "0.5" zbus = {version = "4.1.2", default-features = false, features = ["tokio"], optional = true} fern = "0.6" @@ -68,9 +68,6 @@ unicode-width = "0.1.9" url = "2.5" cursive_buffered_backend = "0.6.1" -[target.'cfg(target_os = "linux")'.dependencies] -wl-clipboard-rs = {version = "0.8", optional = true} - [target.'cfg(unix)'.dependencies] signal-hook = "0.3.0" @@ -102,8 +99,8 @@ pancurses_backend = ["cursive/pancurses-backend", "pancurses/win32"] portaudio_backend = ["librespot-playback/portaudio-backend"] pulseaudio_backend = ["librespot-playback/pulseaudio-backend"] rodio_backend = ["librespot-playback/rodio-backend"] -share_clipboard = ["clipboard", "wl-clipboard-rs"] # Share a link to the system clipboard -share_selection = ["clipboard", "wl-clipboard-rs"] # Use the primary selection for sharing - linux and bsd only +share_clipboard = ["arboard", "arboard/wayland-data-control"] # Share a link to the system clipboard +share_selection = ["arboard", "arboard/wayland-data-control"] # Use the primary selection for sharing - linux and bsd only termion_backend = ["cursive/termion-backend"] [package.metadata.deb] diff --git a/src/sharing.rs b/src/sharing.rs index fe66f4897..6d6607e02 100644 --- a/src/sharing.rs +++ b/src/sharing.rs @@ -1,194 +1,26 @@ #![cfg(feature = "share_clipboard")] -use std::env; - -#[cfg(all(feature = "wl-clipboard-rs", target_os = "linux"))] -use { - std::io::Read, - wl_clipboard_rs::{ - copy, - copy::{Options, Source}, - paste, - paste::{get_contents, Error, Seat}, - }, -}; +use arboard::Clipboard; +use std::error::Error; #[cfg(feature = "share_selection")] -use clipboard::{x11_clipboard, x11_clipboard::X11ClipboardContext}; -#[cfg(all( - feature = "share_selection", - all(feature = "wl-clipboard-rs", target_os = "linux") -))] -use wl_clipboard_rs::utils::{is_primary_selection_supported, PrimarySelectionCheckError}; +use arboard::{GetExtLinux, LinuxClipboardKind, SetExtLinux}; -#[cfg(not(feature = "share_selection"))] -use clipboard::ClipboardContext; -use clipboard::ClipboardProvider; +pub fn read_share() -> Result> { + let mut ctx = Clipboard::new()?; -fn is_wayland() -> bool { - fn session_type() -> String { - env::var("XDG_SESSION_TYPE").unwrap_or_default() - } - fn wl_display() -> String { - env::var("WAYLAND_DISPLAY").unwrap_or_default() - } - fn gdk_backend() -> String { - env::var("GDK_BACKEND").unwrap_or_default() - } - fn current_desktop() -> String { - env::var("XDG_CURRENT_DESKTOP").unwrap_or_default() - } - current_desktop() != "GNOME" - && (session_type().as_str() == "wayland" - || !wl_display().is_empty() - || gdk_backend() == "wayland") -} + #[cfg(feature = "share_selection")] + return Ok(ctx.get().clipboard(LinuxClipboardKind::Primary).text()?); -#[cfg(not(feature = "share_selection"))] -pub fn read_share() -> Option { - if is_wayland() { - #[allow(unused_mut, unused_assignments)] - let mut string = None; - #[cfg(all(feature = "wl-clipboard-rs", target_os = "linux"))] - { - //use wayland clipboard - let result = get_contents( - paste::ClipboardType::Regular, - Seat::Unspecified, - paste::MimeType::Text, - ); - match result { - Ok((mut pipe, _)) => { - let mut contents = vec![]; - pipe.read_to_end(&mut contents).ok(); - string = Some(String::from_utf8_lossy(&contents).to_string()) - } - Err(Error::NoSeats) | Err(Error::ClipboardEmpty) | Err(Error::NoMimeType) => { - //The clipboard is empty or doesn't contain text, nothing to worry about. - string = None - } - Err(err) => { - eprintln!("{err}"); - string = None - } - } - } - string - } else { - //use x11 clipboard - ClipboardProvider::new() - .and_then(|mut ctx: ClipboardContext| ctx.get_contents()) - .ok() - } + #[cfg(not(feature = "share_selection"))] + return Ok(ctx.get_text()?); } -#[cfg(feature = "share_selection")] -pub fn read_share() -> Option { - if is_wayland() { - #[allow(unused_mut, unused_assignments)] - let mut string = None; - #[cfg(all(feature = "wl-clipboard-rs", target_os = "linux"))] - { - //use wayland clipboard - string = match is_primary_selection_supported() { - Ok(_supported) => { - let result = get_contents( - paste::ClipboardType::Primary, - Seat::Unspecified, - paste::MimeType::Text, - ); - match result { - Ok((mut pipe, _)) => { - let mut contents = vec![]; - pipe.read_to_end(&mut contents).ok(); - Some(String::from_utf8_lossy(&contents).to_string()) - } - Err(Error::NoSeats) - | Err(Error::ClipboardEmpty) - | Err(Error::NoMimeType) => { - //The clipboard is empty or doesn't contain text, nothing to worry about. - None - } - Err(err) => { - eprintln!("{}", err); - None - } - } - } - Err(PrimarySelectionCheckError::NoSeats) => { - // Impossible to give a definitive result. Primary selection may or may not be - // supported. +pub fn write_share(url: String) -> Result<(), Box> { + let mut ctx = Clipboard::new()?; - // The required protocol (data-control version 2) is there, but there are no seats. - // Unfortunately, at least one seat is needed to check for the primary clipboard support. - None - } - Err(PrimarySelectionCheckError::MissingProtocol { .. }) => { - // The data-control protocol (required for wl-clipboard-rs operation) is not - // supported by the compositor. - None - } - Err(err) => { - eprintln!("{}", err); - None - } - } - } - string - } else { - //use x11 clipboard - ClipboardProvider::new() - .and_then(|mut ctx: X11ClipboardContext| ctx.get_contents()) - .ok() - } -} + #[cfg(feature = "share_selection")] + return Ok(ctx.set().clipboard(LinuxClipboardKind::Primary).text(url)?); -#[cfg(not(feature = "share_selection"))] -pub fn write_share(url: String) -> Option<()> { - if is_wayland() { - #[allow(unused_mut, unused_assignments)] - let mut option = None; - #[cfg(all(feature = "wl-clipboard-rs", target_os = "linux"))] - { - //use wayland clipboard - let opts = Options::new(); - option = opts - .copy( - Source::Bytes(url.into_bytes().into()), - copy::MimeType::Autodetect, - ) - .ok() - } - option - } else { - //use x11 clipboard - ClipboardProvider::new() - .and_then(|mut ctx: ClipboardContext| ctx.set_contents(url)) - .ok() - } -} - -#[cfg(feature = "share_selection")] -pub fn write_share(url: String) -> Option<()> { - if is_wayland() { - #[allow(unused_mut, unused_assignments)] - let mut option = None; - #[cfg(all(feature = "wl-clipboard-rs", target_os = "linux"))] - { - //use wayland clipboard - let mut opts = Options::new(); - opts.clipboard(copy::ClipboardType::Primary); - option = opts - .copy( - Source::Bytes(url.into_bytes().into()), - copy::MimeType::Autodetect, - ) - .ok() - } - option - } else { - //use x11 clipboard - ClipboardProvider::new() - .and_then(|mut ctx: X11ClipboardContext| ctx.set_contents(url)) - .ok() - } + #[cfg(not(feature = "share_selection"))] + return Ok(ctx.set_text(url)?); } diff --git a/src/ui/contextmenu.rs b/src/ui/contextmenu.rs index 307414967..96161ec5e 100644 --- a/src/ui/contextmenu.rs +++ b/src/ui/contextmenu.rs @@ -305,7 +305,7 @@ impl ContextMenu { } #[cfg(feature = "share_clipboard")] ContextMenuAction::ShareUrl(url) => { - write_share(url.to_string()); + write_share(url.to_string()).ok(); } ContextMenuAction::AddToPlaylist(track) => { let dialog = diff --git a/src/ui/cover.rs b/src/ui/cover.rs index 86a8d6a81..4466841f5 100644 --- a/src/ui/cover.rs +++ b/src/ui/cover.rs @@ -247,7 +247,7 @@ impl ViewExt for CoverView { .and_then(|t| t.as_listitem().share_url()); if let Some(url) = url { - crate::sharing::write_share(url); + crate::sharing::write_share(url).ok(); } return Ok(CommandResult::Consumed(None)); diff --git a/src/ui/listview.rs b/src/ui/listview.rs index 5193ef86f..8712297a7 100644 --- a/src/ui/listview.rs +++ b/src/ui/listview.rs @@ -565,7 +565,7 @@ impl ViewExt for ListView { }; if let Some(url) = url { - write_share(url); + write_share(url).ok(); } return Ok(CommandResult::Consumed(None)); @@ -708,9 +708,9 @@ impl ViewExt for ListView { let url = match source { InsertSource::Input(url) => Some(url.clone()), #[cfg(feature = "share_clipboard")] - InsertSource::Clipboard => { - read_share().and_then(crate::spotify_url::SpotifyUrl::from_url) - } + InsertSource::Clipboard => read_share() + .ok() + .and_then(crate::spotify_url::SpotifyUrl::from_url), }; let spotify = self.queue.get_spotify();