use std::{
collections::HashMap,
io::{Read, Write},
os::{
fd::OwnedFd,
unix::{io::AsFd, net::UnixStream},
},
sync::{
Arc, Mutex, MutexGuard,
atomic::{AtomicBool, Ordering},
mpsc,
},
thread::JoinHandle,
time::Duration,
};
use nix::poll::{PollFd, PollFlags, PollTimeout, poll};
use crate::{
error::{Error, Result},
mime::find_mime_match,
options::{Options, Protocol},
state::{Command, Shared},
worker,
};
const READ_IDLE_TIMEOUT: Duration = Duration::from_secs(5);
#[derive(Debug, Clone, Default)]
pub struct Content {
mime_types: Vec<String>,
data: HashMap<String, Vec<u8>>,
}
impl Content {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn data(mut self, mime_type: impl Into<String>, data: impl Into<Vec<u8>>) -> Self {
let mime_type = mime_type.into();
self.push_type(&mime_type);
self.data.insert(mime_type, data.into());
self
}
#[must_use]
pub fn advertise(mut self, mime_type: impl Into<String>) -> Self {
self.push_type(&mime_type.into());
self
}
fn push_type(&mut self, mime_type: &str) {
if !self.mime_types.iter().any(|m| m == mime_type) {
self.mime_types.push(mime_type.to_owned());
}
}
}
struct Link {
commands: mpsc::Sender<Command>,
wake: UnixStream,
}
impl Link {
fn send(&self, command: Command) -> Result<()> {
self.commands.send(command).map_err(|_| Error::Stopped)?;
self.notify();
Ok(())
}
fn notify(&self) {
let _ = (&self.wake).write(&[1]);
}
}
#[derive(Debug)]
#[must_use = "a paste stays open until the request is answered"]
pub struct TransferRequest {
serial: u32,
mime_type: String,
link: Arc<Link>,
}
impl std::fmt::Debug for Link {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Link").finish_non_exhaustive()
}
}
impl TransferRequest {
#[must_use]
pub fn mime_type(&self) -> &str {
&self.mime_type
}
pub fn complete(self, data: impl Into<Vec<u8>>) -> Result<()> {
self.link.send(Command::CompleteTransfer {
serial: self.serial,
data: Some(data.into()),
})
}
pub fn fail(self) -> Result<()> {
self.link.send(Command::CompleteTransfer {
serial: self.serial,
data: None,
})
}
}
pub struct DataControl {
link: Arc<Link>,
shared: Arc<Mutex<Shared>>,
protocol: Protocol,
max_read_bytes: usize,
stop: Arc<AtomicBool>,
thread: Option<JoinHandle<()>>,
}
impl std::fmt::Debug for DataControl {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DataControl")
.field("protocol", &self.protocol)
.finish_non_exhaustive()
}
}
impl DataControl {
pub fn connect() -> Result<Self> {
Self::connect_with(&Options::new())
}
pub fn connect_with(options: &Options) -> Result<Self> {
let (command_tx, command_rx) = mpsc::channel();
let (wake_tx, wake_rx) = UnixStream::pair()?;
wake_tx.set_nonblocking(true)?;
wake_rx.set_nonblocking(true)?;
let stop = Arc::new(AtomicBool::new(false));
let (ready_tx, ready_rx) = mpsc::sync_channel(1);
let thread_options = options.clone();
let thread_stop = Arc::clone(&stop);
let thread = std::thread::Builder::new()
.name("lamco-data-control".into())
.spawn(move || {
match worker::connect(&thread_options, command_rx, wake_rx, thread_stop) {
Ok((worker, connected)) => {
if ready_tx.send(Ok(connected)).is_ok() {
worker.run();
}
}
Err(error) => {
let _ = ready_tx.send(Err(error));
}
}
})?;
let connected = match ready_rx.recv() {
Ok(Ok(connected)) => connected,
Ok(Err(error)) => {
let _ = thread.join();
return Err(error);
}
Err(_) => {
let _ = thread.join();
return Err(Error::Stopped);
}
};
Ok(Self {
link: Arc::new(Link {
commands: command_tx,
wake: wake_tx,
}),
shared: connected.shared,
protocol: connected.protocol,
max_read_bytes: options.max_read_bytes,
stop,
thread: Some(thread),
})
}
#[must_use]
pub fn protocol(&self) -> Protocol {
self.protocol
}
fn shared(&self) -> MutexGuard<'_, Shared> {
self.shared
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
#[must_use]
pub fn selection_mime_types(&self) -> Vec<String> {
self.shared().mime_types.clone()
}
#[must_use]
pub fn serial(&self) -> u32 {
self.shared().serial
}
pub fn on_change(&self, callback: impl Fn(Vec<String>) + Send + Sync + 'static) {
self.shared().on_change = Some(Arc::new(callback));
}
pub fn on_transfer(&self, callback: impl Fn(TransferRequest) + Send + Sync + 'static) {
let link = Arc::clone(&self.link);
self.shared().on_transfer = Some(Arc::new(move |serial, mime_type| {
callback(TransferRequest {
serial,
mime_type,
link: Arc::clone(&link),
});
}));
}
pub fn set_selection(&self, content: Content) -> Result<()> {
self.link.send(Command::SetSelection {
mime_types: content.mime_types,
data: content.data,
})
}
pub fn update_data(
&self,
mime_type: impl Into<String>,
data: impl Into<Vec<u8>>,
) -> Result<()> {
self.link.send(Command::UpdateSourceData {
mime_type: mime_type.into(),
data: data.into(),
})
}
pub fn clear_selection(&self) -> Result<()> {
self.link.send(Command::ClearSelection)
}
pub fn read(&self, mime_type: &str) -> Result<Option<Vec<u8>>> {
let available = self.selection_mime_types();
let Some(matched) = find_mime_match(mime_type, &available) else {
return Ok(None);
};
let (mut reader, writer) = std::io::pipe()?;
self.link.send(Command::ReceiveFromOffer {
mime_type: matched.to_owned(),
fd: OwnedFd::from(writer),
})?;
let mut data = Vec::new();
let mut chunk = vec![0u8; 64 * 1024];
loop {
let mut fds = [PollFd::new(reader.as_fd(), PollFlags::POLLIN)];
let timeout = PollTimeout::try_from(READ_IDLE_TIMEOUT).unwrap_or(PollTimeout::NONE);
match poll(&mut fds, timeout) {
Ok(0) => return Err(Error::Timeout),
Ok(_) => {}
Err(nix::errno::Errno::EINTR) => continue,
Err(errno) => return Err(std::io::Error::from(errno).into()),
}
match reader.read(&mut chunk) {
Ok(0) => return Ok(Some(data)),
Ok(n) => {
if data.len() + n > self.max_read_bytes {
return Err(Error::TooLarge {
size: data.len() + n,
limit: self.max_read_bytes,
});
}
data.extend_from_slice(&chunk[..n]);
}
Err(error) if error.kind() == std::io::ErrorKind::Interrupted => {}
Err(error) => return Err(error.into()),
}
}
}
}
impl Drop for DataControl {
fn drop(&mut self) {
self.stop.store(true, Ordering::Relaxed);
self.link.notify();
if let Some(thread) = self.thread.take() {
let _ = thread.join();
}
}
}