use std::sync::mpsc;
pub trait Primary: Send {
fn set(&mut self, text: &str) -> Result<(), String>;
}
pub fn system() -> Box<dyn Primary> {
Box::new(System::default())
}
#[derive(Default)]
struct System {
#[cfg(all(unix, not(target_os = "macos")))]
clipboard: Option<arboard::Clipboard>,
}
impl Primary for System {
#[cfg(all(unix, not(target_os = "macos")))]
fn set(&mut self, text: &str) -> Result<(), String> {
use arboard::{LinuxClipboardKind, SetExtLinux};
let clipboard = match &mut self.clipboard {
Some(c) => c,
None => self
.clipboard
.insert(arboard::Clipboard::new().map_err(|e| e.to_string())?),
};
clipboard
.set()
.clipboard(LinuxClipboardKind::Primary)
.text(text)
.map_err(|e| e.to_string())
}
#[cfg(not(all(unix, not(target_os = "macos"))))]
fn set(&mut self, _text: &str) -> Result<(), String> {
Err("no primary selection here".into())
}
}
#[cfg(not(target_arch = "wasm32"))]
pub struct NoPrimary;
#[cfg(not(target_arch = "wasm32"))]
impl Primary for NoPrimary {
fn set(&mut self, _text: &str) -> Result<(), String> {
Err("not set in a snapshot".into())
}
}
pub trait ReadPrimary {
fn get(&mut self) -> Option<String>;
}
pub type MakeRead = Box<dyn FnOnce() -> Box<dyn ReadPrimary> + Send>;
pub fn system_reader() -> Option<MakeRead> {
#[cfg(all(unix, not(target_os = "macos")))]
{
Some(Box::new(|| Box::new(SystemRead)))
}
#[cfg(not(all(unix, not(target_os = "macos"))))]
{
None
}
}
#[cfg(all(unix, not(target_os = "macos")))]
struct SystemRead;
#[cfg(all(unix, not(target_os = "macos")))]
impl ReadPrimary for SystemRead {
fn get(&mut self) -> Option<String> {
use arboard::{GetExtLinux, LinuxClipboardKind};
arboard::Clipboard::new()
.inspect_err(|e| log::debug!("no primary selection: {e}"))
.ok()?
.get()
.clipboard(LinuxClipboardKind::Primary)
.text()
.inspect_err(|e| log::debug!("could not read the primary selection: {e}"))
.ok()
.filter(|t| !t.trim().is_empty())
}
}
pub struct Reader<T> {
jobs: mpsc::Sender<T>,
answers: mpsc::Receiver<(T, Option<String>)>,
}
impl<T: Clone + Send + 'static> Reader<T> {
pub fn spawn(make: MakeRead, wake: impl Fn(&T) + Send + 'static) -> Self {
let (jobs, rx) = mpsc::channel::<T>();
let (tx, answers) = mpsc::channel();
let spawned = std::thread::Builder::new()
.name("bobview-selection".into())
.spawn(move || {
let mut primary = make();
for tag in rx {
let text = primary.get();
if tx.send((tag.clone(), text)).is_err() {
break;
}
wake(&tag);
}
});
if let Err(e) = spawned {
log::warn!("no thread to read the primary selection: {e}");
}
Self { jobs, answers }
}
pub fn read(&self, tag: T) {
let _ = self.jobs.send(tag);
}
pub fn answer(&self) -> Option<(T, Option<String>)> {
self.answers.try_recv().ok()
}
}