use std::fmt;
use std::future::Future;
use std::io;
use std::path::PathBuf;
use std::pin::Pin;
use std::rc::Rc;
use std::time::Duration;
pub const URI_LIST: &str = "text/uri-list";
pub trait Clipboard {
fn items(&self) -> ClipboardItemsFuture;
fn write(&self, items: Vec<ClipboardItem>, options: CopyOptions) -> ClipboardWriteFuture;
}
pub type ClipboardItemsFuture = Pin<Box<dyn Future<Output = Vec<Box<dyn DataTransferItem>>>>>;
pub type ClipboardWriteFuture = Pin<Box<dyn Future<Output = Result<(), ClipboardError>>>>;
pub type ClipboardReadFuture = Pin<Box<dyn Future<Output = Result<Vec<u8>, ClipboardError>>>>;
pub type ContentFuture = Pin<Box<dyn Future<Output = Option<Vec<u8>>>>>;
pub type WriteFuture = Pin<Box<dyn Future<Output = io::Result<()>>>>;
pub trait FileSink {
fn write<'a>(
&'a mut self,
bytes: &'a [u8],
) -> Pin<Box<dyn Future<Output = io::Result<()>> + 'a>>;
}
pub trait DataTransferItem {
fn types(&self) -> &[String];
fn read(&self, mime: &str) -> ClipboardReadFuture;
}
pub struct ClipboardItem {
pub representations: Vec<(String, Content)>,
}
pub enum Content {
Data(Vec<u8>),
Promise(Rc<dyn Fn() -> ContentFuture>),
File(PathBuf),
FilePromise {
name: String,
produce: Rc<dyn Fn(Box<dyn FileSink>) -> WriteFuture>,
},
}
impl fmt::Debug for Content {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Content::Data(bytes) => f.debug_tuple("Data").field(&bytes.len()).finish(),
Content::Promise(_) => f.write_str("Promise"),
Content::File(path) => f.debug_tuple("File").field(path).finish(),
Content::FilePromise { name, .. } => {
f.debug_struct("FilePromise").field("name", name).finish()
}
}
}
}
impl fmt::Debug for ClipboardItem {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ClipboardItem")
.field("representations", &self.representations)
.finish()
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct CopyOptions {
pub sensitive: bool,
pub this_device_only: bool,
pub expires_after: Option<Duration>,
}
impl CopyOptions {
pub const fn new() -> CopyOptions {
CopyOptions {
sensitive: false,
this_device_only: false,
expires_after: None,
}
}
pub const fn sensitive(mut self, sensitive: bool) -> CopyOptions {
self.sensitive = sensitive;
self
}
pub const fn this_device_only(mut self, this_device_only: bool) -> CopyOptions {
self.this_device_only = this_device_only;
self
}
pub const fn expires_after(mut self, expires_after: Duration) -> CopyOptions {
self.expires_after = Some(expires_after);
self
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ClipboardError {
Changed,
NotFound,
Denied,
Unsupported,
Failed(String),
}
impl fmt::Display for ClipboardError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ClipboardError::Changed => f.write_str("the clipboard changed since it was listed"),
ClipboardError::NotFound => f.write_str("the item is not offered in that type"),
ClipboardError::Denied => f.write_str("the clipboard may not be reached"),
ClipboardError::Unsupported => f.write_str("the system cannot carry that form"),
ClipboardError::Failed(reason) => write!(f, "the clipboard failed: {reason}"),
}
}
}
impl std::error::Error for ClipboardError {}
pub enum ClipboardEvent {
Copy(CopyRequest),
Cut(CopyRequest),
Paste(Vec<Box<dyn DataTransferItem>>),
}
impl fmt::Debug for ClipboardEvent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ClipboardEvent::Copy(_) => f.write_str("Copy"),
ClipboardEvent::Cut(_) => f.write_str("Cut"),
ClipboardEvent::Paste(items) => f.debug_tuple("Paste").field(&items.len()).finish(),
}
}
}
pub struct CopyRequest(Box<dyn Fn(Vec<ClipboardItem>, CopyOptions) -> ClipboardWriteFuture>);
impl CopyRequest {
pub fn new(
write: impl Fn(Vec<ClipboardItem>, CopyOptions) -> ClipboardWriteFuture + 'static,
) -> CopyRequest {
CopyRequest(Box::new(write))
}
pub fn write(&self, items: Vec<ClipboardItem>, options: CopyOptions) -> ClipboardWriteFuture {
(self.0)(items, options)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn copy_options_start_as_any_other_copy_and_set_one_option_each() {
assert_eq!(CopyOptions::new(), CopyOptions::default());
let options = CopyOptions::new()
.sensitive(true)
.expires_after(Duration::from_secs(60));
assert!(options.sensitive);
assert!(!options.this_device_only);
assert_eq!(options.expires_after, Some(Duration::from_secs(60)));
}
#[test]
fn a_copy_request_writes_through_what_it_was_made_with() {
use std::cell::RefCell;
let written = Rc::new(RefCell::new(Vec::new()));
let into = Rc::clone(&written);
let request = CopyRequest::new(move |items, _options| {
into.borrow_mut().extend(items);
Box::pin(std::future::ready(Ok(())))
});
let item = ClipboardItem {
representations: vec![("text/plain".to_owned(), Content::Data(b"hi".to_vec()))],
};
let mut write = request.write(vec![item], CopyOptions::new());
let mut context = std::task::Context::from_waker(std::task::Waker::noop());
let done = write.as_mut().poll(&mut context);
assert_eq!(done, std::task::Poll::Ready(Ok(())));
assert_eq!(written.borrow().len(), 1);
}
}