a simple clipboard for complicated times
Diffstat (limited to 'src/providers.rs')
-rw-r--r--src/providers.rs231
1 files changed, 231 insertions, 0 deletions
diff --git a/src/providers.rs b/src/providers.rs
new file mode 100644
index 0000000..5c9c586
--- /dev/null
+++ b/src/providers.rs
@@ -0,0 +1,231 @@
+//! implements different clipboard types
+use std::{
+ io::{Read, Write},
+ process::{Command, Stdio},
+};
+
+pub trait Clipboard {
+ fn copy(text: &str);
+ fn paste() -> String;
+}
+
+macro_rules! c {
+ ($p:ident $($args:ident)+) => {
+ Command::new(stringify!($p)).args([$(stringify!($args),)+])
+ };
+ ($p:literal) => {
+ Command::new($p)
+ };
+ ($p:literal $($args:literal)+) => {
+ Command::new($p).args([$($args,)+])
+
+ }
+}
+
+trait Eat {
+ fn eat(&mut self) -> String;
+}
+
+impl Eat for Command {
+ fn eat(&mut self) -> String {
+ let mut s = String::new();
+ self.stdout(Stdio::piped())
+ .spawn()
+ .expect("spawn ok")
+ .stdout
+ .take()
+ .unwrap()
+ .read_to_string(&mut s)
+ .expect("read ok");
+ s
+ }
+}
+
+trait Put {
+ fn put(&mut self, s: impl AsRef<[u8]>);
+}
+
+impl Put for Command {
+ fn put(&mut self, s: impl AsRef<[u8]>) {
+ let mut ch = self.stdin(Stdio::piped()).spawn().expect("spawn ok");
+ ch.stdin
+ .take()
+ .unwrap()
+ .write_all(s.as_ref())
+ .expect("write ok");
+ ch.wait().expect("proc ok");
+ }
+}
+
+#[cfg(target_os = "macos")]
+pub struct PbCopy {}
+#[cfg(target_os = "macos")]
+impl Clipboard for PbCopy {
+ fn copy(text: &str) {
+ c!(pbcopy w).put(text)
+ }
+
+ fn paste() -> String {
+ c!(pbcopy r).eat()
+ }
+}
+
+pub struct XClip {}
+impl Clipboard for XClip {
+ fn copy(text: &str) {
+ c!("xclip" "-selection" "c").put(text);
+ }
+
+ fn paste() -> String {
+ c!("xclip" "-selection" "c" "-o") // xcclip is complainy
+ .stderr(Stdio::null())
+ .stdout(Stdio::null())
+ .eat()
+ }
+}
+
+pub struct XSel {}
+impl Clipboard for XSel {
+ fn copy(text: &str) {
+ c!("xsel" "-b" "-i").put(text);
+ }
+
+ fn paste() -> String {
+ c!("xsel" "-b" "-o").eat()
+ }
+}
+
+struct Wayland {}
+impl Clipboard for Wayland {
+ fn copy(text: &str) {
+ match text {
+ "" => assert!(
+ c!("wl-copy" "-p" "--clear").status().unwrap().success(),
+ "wl-copy fail"
+ ),
+ s => c!("wl-copy" "-p").put(s),
+ }
+ }
+
+ fn paste() -> String {
+ c!("wl-paste" "-n" "-p").eat()
+ }
+}
+
+struct Klipper {}
+impl Clipboard for Klipper {
+ fn copy(text: &str) {
+ c!("qdbus" "org.kde.klipper" "/klipper" "setClipboardContents").arg(text);
+ }
+
+ fn paste() -> String {
+ let mut s = c!("qdbus" "org.kde.klipper" "/klipper" "getClipboardContents").eat();
+ assert!(s.ends_with('\n'));
+ s.truncate(s.len() - 1);
+ s
+ }
+}
+
+#[cfg(target_family = "windows")]
+struct Windows {}
+#[cfg(target_family = "windows")]
+impl Clipboard for Windows {
+ fn copy(text: &str) {
+ clipboard_win::set_clipboard_string(text).expect("set clip ok")
+ }
+
+ fn paste() -> String {
+ clipboard_win::get_clipboard_string().expect("get clip ok")
+ }
+}
+
+struct Wsl {}
+
+impl Clipboard for Wsl {
+ fn copy(text: &str) {
+ c!("clip.exe").put(text);
+ }
+
+ fn paste() -> String {
+ let mut s = c!("powershell.exe" "-noprofile" "-command" "Get-Clipboard").eat();
+ s.truncate(s.len() - 2); // \r\n
+ s
+ }
+}
+
+pub type Board = (for<'a> fn(&'a str), fn() -> String);
+
+fn get<T: Clipboard>() -> Board {
+ (T::copy, T::paste)
+}
+
+fn has(c: &str) -> bool {
+ c!("which")
+ .arg(c)
+ .stdout(Stdio::null())
+ .stderr(Stdio::null())
+ .status()
+ .expect("ok")
+ .success()
+}
+
+fn wsl() -> bool {
+ if let Ok(s) = std::fs::read_to_string("/proc/version") {
+ if s.to_lowercase().contains("microsoft") {
+ return true;
+ }
+ }
+ false
+}
+
+pub fn provide() -> Board {
+ #[cfg(target_family = "windows")]
+ return get::<Windows>();
+ #[cfg(target_os = "macos")]
+ return get::<PbCopy>();
+
+ if wsl() {
+ return get::<Wsl>();
+ }
+ assert!(std::env::var("DISPLAY").is_ok(), "no clipboard available");
+ if std::env::var("WAYLAND_DISPLAY").is_ok() && has("wl-copy") {
+ get::<Wayland>()
+ } else if has("xsel") {
+ get::<XSel>()
+ } else if has("xclip") {
+ get::<XClip>()
+ } else if has("klipper") && has("qdbus") {
+ get::<Klipper>()
+ } else {
+ panic!("no clipboard available");
+ }
+}
+
+#[test]
+fn test() {
+ macro_rules! test {
+ ($clipboard:ty) => {
+ <$clipboard>::copy("text");
+ assert_eq!(<$clipboard>::paste(), "text");
+ <$clipboard>::copy("");
+ };
+ }
+ #[cfg(target_os = "macos")]
+ test!(PbCopy);
+ #[cfg(target_os = "linux")]
+ test!(XClip);
+ #[cfg(target_os = "linux")]
+ test!(XSel);
+ #[cfg(target_os = "linux")]
+ if std::env::var("WAYLAND_DISPLAY").is_ok() {
+ test!(Wayland);
+ }
+ #[cfg(target_os = "linux")]
+ test!(Klipper);
+ #[cfg(target_family = "windows")]
+ test!(Windows);
+ if wsl() {
+ #[cfg(target_os = "linux")]
+ test!(Wsl);
+ }
+}