Skip to main content

ferroui_native/
clipboard_impl.rs

1use crate::clipboard_data_transfer::ClipboardDataTransfer;
2use crate::clipboard_read_session::ClipboardReadSession;
3use crate::data_transfer_to_frn_clipboard_data_source_wrapper::DataTransferToFrnClipboardDataSourceWrapper;
4use crate::helpers::ClipboardResultExt;
5use crate::interop::*;
6use ferroui_base::input::platform::{ClipboardError, IClipboardImpl, IOwnedClipboardImpl};
7use ferroui_base::input::{IAsyncDataTransfer, LocalBoxFuture};
8use ferroui_base::logging::LogArea;
9use ferroui_base::reactive::IDisposable;
10use ferroui_microcom::ComPtr;
11use std::cell::{Cell, RefCell};
12use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
13use std::rc::Rc;
14
15/// The system clipboard.
16pub struct ClipboardImpl {
17    native: RefCell<Option<ComPtr<IFrnClipboard>>>,
18    last_clear_change_count: Cell<i64>,
19}
20
21/// Runs `operation` now and returns its outcome as a completed future: the
22/// value or the error of the native clipboard call that failed (the COM
23/// exception of the reference). A panic (the clipboard was disposed) is
24/// raised when the future is awaited, like a faulted task.
25fn completed<T: 'static>(
26    operation: impl FnOnce() -> Result<T, ClipboardError>,
27) -> LocalBoxFuture<Result<T, ClipboardError>> {
28    match catch_unwind(AssertUnwindSafe(operation)) {
29        Ok(value) => Box::pin(std::future::ready(value)),
30        Err(payload) => Box::pin(async move { resume_unwind(payload) }),
31    }
32}
33
34impl ClipboardImpl {
35    pub fn new(native: ComPtr<IFrnClipboard>) -> ClipboardImpl {
36        ClipboardImpl { native: RefCell::new(Some(native)), last_clear_change_count: Cell::new(i64::MIN) }
37    }
38
39    #[track_caller]
40    pub(crate) fn native(&self) -> ComPtr<IFrnClipboard> {
41        match self.native.borrow().clone() {
42            Some(native) => native,
43            None => panic!("Cannot access a disposed object: ClipboardImpl"),
44        }
45    }
46
47    fn clear_core(&self) -> Result<(), ClipboardError> {
48        self.last_clear_change_count.set(self.native().clear().clipboard_check()?);
49        Ok(())
50    }
51
52    fn try_get_data(&self) -> Result<Option<Rc<dyn IAsyncDataTransfer>>, ClipboardError> {
53        let native = self.native();
54        let change_count = native.get_change_count().clipboard_check()?;
55        let data_transfer = ClipboardDataTransfer::new(ClipboardReadSession::new(native, change_count));
56
57        if data_transfer.try_formats()?.is_empty() {
58            data_transfer.dispose();
59            return Ok(None);
60        }
61
62        Ok(Some(data_transfer))
63    }
64
65    fn set_data(&self, data_transfer: Rc<dyn IAsyncDataTransfer>) -> Result<(), ClipboardError> {
66        self.clear_core()?;
67
68        let source = IFrnClipboardDataSource::from_impl(DataTransferToFrnClipboardDataSourceWrapper::new(
69            data_transfer.to_synchronous(LogArea::MACOS_PLATFORM),
70        ));
71        self.native().set_data(Some(&source)).clipboard_check()
72    }
73
74    /// Releases the native clipboard.
75    pub fn dispose(&self) {
76        let native = self.native.borrow_mut().take();
77        drop(native);
78    }
79}
80
81impl IClipboardImpl for ClipboardImpl {
82    fn try_get_data_async(&self) -> LocalBoxFuture<Result<Option<Rc<dyn IAsyncDataTransfer>>, ClipboardError>> {
83        completed(|| self.try_get_data())
84    }
85
86    fn set_data_async(&self, data_transfer: Rc<dyn IAsyncDataTransfer>) -> LocalBoxFuture<Result<(), ClipboardError>> {
87        completed(|| self.set_data(data_transfer))
88    }
89
90    fn clear_async(&self) -> LocalBoxFuture<Result<(), ClipboardError>> {
91        completed(|| self.clear_core())
92    }
93
94    fn as_owned_clipboard_impl(&self) -> Option<&dyn IOwnedClipboardImpl> {
95        Some(self)
96    }
97}
98
99impl IOwnedClipboardImpl for ClipboardImpl {
100    fn is_current_owner_async(&self) -> LocalBoxFuture<Result<bool, ClipboardError>> {
101        let is_current_owner = self
102            .native()
103            .get_change_count()
104            .clipboard_check()
105            .map(|change_count| change_count == self.last_clear_change_count.get());
106        Box::pin(std::future::ready(is_current_owner))
107    }
108}