use std::cell::RefCell;
use std::fmt;
pub const MAX_TEXT_BYTES: usize = 64 * 1024;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ClipboardError {
TooLong { len: usize },
}
impl fmt::Display for ClipboardError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ClipboardError::TooLong { len } => write!(
f,
"clipboard text is {len} bytes, longer than the {MAX_TEXT_BYTES} bytes one read or write may carry"
),
}
}
}
impl std::error::Error for ClipboardError {}
thread_local! {
static QUEUE: RefCell<Vec<String>> = const { RefCell::new(Vec::new()) };
}
pub fn set_text(text: &str) -> Result<(), ClipboardError> {
if text.len() > MAX_TEXT_BYTES {
return Err(ClipboardError::TooLong { len: text.len() });
}
QUEUE.with_borrow_mut(|queue| queue.push(text.to_owned()));
Ok(())
}
pub(crate) fn drain(mut emit: impl FnMut(&str)) {
let mut taken = QUEUE.with_borrow_mut(std::mem::take);
if taken.is_empty() {
return;
}
for text in &taken {
emit(text);
}
taken.clear();
QUEUE.with_borrow_mut(|queue| {
if queue.is_empty() {
*queue = taken;
}
});
}
#[doc(hidden)]
pub fn reset_clipboard() {
QUEUE.with_borrow_mut(Vec::clear);
READS.with_borrow_mut(Vec::clear);
WAITING.with_borrow_mut(Vec::clear);
}
pub mod read_status {
pub const NONE: u32 = 0;
pub const TEXT: u32 = 1;
pub const TOO_LONG: u32 = 2;
}
pub type ReadResult = Result<Option<String>, ClipboardError>;
type ReadCallback = Box<dyn FnOnce(ReadResult)>;
thread_local! {
static READS: RefCell<Vec<u32>> = const { RefCell::new(Vec::new()) };
static WAITING: RefCell<Vec<(u32, ReadCallback)>> = const { RefCell::new(Vec::new()) };
static NEXT_READ: std::cell::Cell<u32> = const { std::cell::Cell::new(1) };
}
fn next_read() -> u32 {
NEXT_READ.with(|next| {
let mut id = next.get();
if id == 0 {
id = 1;
}
next.set(id.wrapping_add(1));
id
})
}
pub fn read_text(on_text: impl FnOnce(ReadResult) + 'static) {
let request = next_read();
WAITING.with_borrow_mut(|waiting| waiting.push((request, Box::new(on_text))));
READS.with_borrow_mut(|reads| reads.push(request));
if !crate::boundary::in_host_call() {
crate::boundary::request_frame_from_worker();
}
}
pub fn read_text_async() -> impl std::future::Future<Output = ReadResult> {
ReadFuture { shared: None }
}
#[derive(Default)]
struct ReadShared {
result: Option<ReadResult>,
waker: Option<std::task::Waker>,
}
struct ReadFuture {
shared: Option<std::rc::Rc<RefCell<ReadShared>>>,
}
impl std::future::Future for ReadFuture {
type Output = ReadResult;
fn poll(
mut self: std::pin::Pin<&mut Self>,
context: &mut std::task::Context<'_>,
) -> std::task::Poll<ReadResult> {
let shared = match &self.shared {
Some(shared) => std::rc::Rc::clone(shared),
None => {
let shared = std::rc::Rc::new(RefCell::new(ReadShared::default()));
let answer = std::rc::Rc::clone(&shared);
read_text(move |result| {
let waker = {
let mut answer = answer.borrow_mut();
answer.result = Some(result);
answer.waker.take()
};
if let Some(waker) = waker {
waker.wake();
}
});
self.shared = Some(std::rc::Rc::clone(&shared));
shared
}
};
let mut state = shared.borrow_mut();
match state.result.take() {
Some(result) => std::task::Poll::Ready(result),
None => {
state.waker = Some(context.waker().clone());
std::task::Poll::Pending
}
}
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Clipboard {
_private: (),
}
impl Clipboard {
pub fn read_text(&self) -> impl std::future::Future<Output = ReadResult> {
read_text_async()
}
pub fn read_text_with(&self, on_text: impl FnOnce(ReadResult) + 'static) {
read_text(on_text);
}
pub fn set_text(&self, text: &str) -> Result<(), ClipboardError> {
set_text(text)
}
}
pub fn use_clipboard() -> Clipboard {
Clipboard::default()
}
pub(crate) fn drain_reads(mut emit: impl FnMut(u32)) {
let taken = READS.with_borrow_mut(std::mem::take);
for request in taken {
emit(request);
}
}
pub(crate) fn deliver_read(request: u32, status: u32, len: u32, text: &str) -> bool {
let result = match status {
read_status::TEXT if text.len() > MAX_TEXT_BYTES => {
Err(ClipboardError::TooLong { len: text.len() })
}
read_status::TEXT if text.is_empty() => Ok(None),
read_status::TEXT => Ok(Some(text.to_owned())),
read_status::TOO_LONG => Err(ClipboardError::TooLong {
len: (len as usize).max(MAX_TEXT_BYTES + 1),
}),
_ => Ok(None),
};
let callback = WAITING.with_borrow_mut(|waiting| {
waiting
.iter()
.position(|(id, _)| *id == request)
.map(|at| waiting.remove(at).1)
});
match callback {
Some(callback) => {
callback(result);
true
}
None => false,
}
}